web-dev-qa-db-ja.com

C ++:関連付け、集約、および構成

OOADの勉強を始めましたが、C++AssociationAggregation、およびCompositionをプログラムで実装する方法を示すコード例。 (どこにでもいくつかの投稿がありますが、それらはC#またはJavaに関連しています)。私は1つか2つの例を見つけましたが、それらはすべて私のインストラクターの指示と矛盾しており、私は混乱しています。

私の理解は:

  • Association: Fooには、データメンバーとしてBarオブジェクトへのポインターがあります
  • Aggregation: FooにはBarオブジェクトへのポインターがあり、Barのデータはそのポインターにディープコピーされます。
  • 構成: Fooには、データメンバーとしてBarオブジェクトがあります。

そして、これは私がそれを実装した方法です:

//ASSOCIATION
class Bar
{
    Baz baz;
};
class Foo
{
    Bar* bar;
    void setBar(Bar* _bar)
    {
        bar=_bar;
    }
};

//AGGREGATION
class Bar
{
    Baz baz;
};
class Foo
{
    Bar* bar;
    void setBar(Bar* _bar)
    {
        bar = new Bar;
        bar->baz=_bar->baz;
    }
};


//COMPOSTION
class Bar
{
    Baz baz;
};
class Foo
{
    Bar bar;
    Foo(Baz baz)
    {
        bar.baz=baz;
    }
};

これは正しいです?そうでない場合、代わりにどのように行う必要がありますか?本のコードの参照も教えていただければ幸いです(インストラクターと話し合うため)

21
Talha5389

集計を無視します。それは 非常に明確に定義された概念ではない であり、私の意見では、価値がある以上に混乱を引き起こします。作曲と連想は十分です Craig Larman そう言った。インストラクターが求めていた答えではないかもしれませんが、とにかく異なる方法でC++に実装することはほとんどありません。

構成と関連付けを実装する方法は1つではありません。それらの実装方法は、関係の多様性などの要件によって異なります。

組成

構成を実装する最も簡単な方法は、あなたが提案したような単純なBarメンバー変数を使用することです。私が行う唯一の変更は、コンストラクタメンバー初期化子リストのbarを初期化することです。

// COMPOSITION - with simple member variable
class Foo {
 private:
  Bar bar;
 public:   
  Foo(int baz) : bar(baz) {}
};

一般に、コンストラクターの初期化リストを使用してメンバー変数を初期化することをお勧めします。これはより高速であり、constメンバー変数のように初期化する唯一の方法です。

ポインタを使用して構成を実装する理由もあります。たとえば、Barは多相型であり、コンパイル時に具体的な型がわからないことがあります。または、コンパイルの依存関係を最小限に抑えるためにBarを前方宣言することもできます( PIMPL idiom を参照)。または、この関係の多重度は1〜0..1であり、null Barを持つことができる必要があります。もちろん、これはコンポジションFooBarを所有する必要があるため、C++ 11/C++ 14の現代の世界では、生のポインターを所有するのではなく、スマートポインターを使用することを好みます。

 // COMPOSITION - with unique_ptr
class Foo {
 private:
  std::unique_ptr<Bar> bar;
 public:   
  Foo(int baz) : bar(barFactory(baz)) {}
};

FooBarの唯一の所有者であるため、ここでstd::unique_ptrを使用しましたが、他のオブジェクトがstd::shared_ptrを必要とする場合はstd::weak_ptrを使用することができますBarに。

協会

関連付けは通常、あなたが行ったようにポインタを使用して実装されます。

// Association - with non-owning raw pointer
class Foo {
 private:
  Bar* bar;
 public:   
  void setBar(Bar* b) { bar = b; }
};

もちろん、Barが使用している間にFooが生きていることを確信する必要があります。 Barのライフタイムがそれほど明確でない場合、std::weak_ptrの方が適切かもしれません。

// Association - with weak pointer
class Foo {
 private:
  std::weak_ptr<Bar> bar;
 public:   
  void setBar(std::weak_ptr<Bar> b) { bar = std::move(b); }
  void useBar() {
    auto b = bar.lock();
    if (b)
      std::cout << b->baz << "\n";
  }
};

これで、Fooは、ぶら下がりポインタが残ることを恐れずにBarを使用できます。

 Foo foo;
 {
   auto bar = std::make_shared<Bar>(3);
   foo.setBar(bar);
   foo.useBar(); // ok
 }  
 foo.useBar(); // bar has gone but it is ok

Barの所有権が本当に不明な場合には、std::shared_ptrを使用してAssociationを実装できますが、それは最後の手段であると思います。

Barポインターのディープコピーを使用した集計の実装について。私はそれが典型的な実装だとは言わなかったでしょうが、私が言ったように、それはあなたの要件に依存します。 deleteデストラクタのbarメンバーポインタでFooを呼び出すことを確認する必要がありますが、そうでない場合はメモリリークが発生します(またはスマートポインタを使用します)。

16
Chris Drew