web-dev-qa-db-fra.com

C ++: Association, agrégation et composition

Je commence à étudier OOAD et j'ai du mal à trouver un C++ exemple de code qui illustrerait comment Association, Aggregation et Composition sont implémentés par programme. (Il y a plusieurs articles partout mais ils concernent C # ou Java). J'ai trouvé un exemple ou deux, mais ils sont tous en conflit avec les instructions de mon instructeur et je suis confus.

Ma compréhension est que dans:

  • Association: Foo a un pointeur sur l'objet Bar en tant que membre de données
  • Agrégation: Foo a un pointeur sur l'objet Bar et les données de Bar sont copiées en profondeur dans ce pointeur.
  • Composition: Foo a un objet Bar comme membre de données.

Et voici comment je l'ai implémenté:

//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;
    }
};

Est-ce correct? Sinon, comment faut-il procéder à la place? Ce serait apprécié si vous me fournissez également une référence d'un code d'un livre (afin que je puisse discuter avec mon instructeur)

21
Talha5389

Je vais ignorer l'agrégation. C'est pas un concept très clairement défini et à mon avis, cela crée plus de confusion qu'il n'en vaut la peine. La composition et l'association suffisent, Craig Larman me l'a dit. Ce n'est peut-être pas la réponse que votre instructeur recherchait, mais il est peu probable qu'elle soit implémentée différemment en C++.

Il n'y a pas une façon de mettre en œuvre la composition et l'association. La façon dont vous les mettrez en œuvre dépendra de vos besoins, par exemple la multiplicité de la relation.

Composition

La manière la plus simple d'implémenter la composition est d'utiliser une simple variable membre Bar tout comme vous l'avez suggéré. La seule modification que j'apporterais serait d'initialiser le bar dans la liste d'initialisation des membres constructeurs:

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

C'est généralement une bonne idée d'initialiser les variables membres en utilisant la liste d'initialisation du constructeur, cela peut être plus rapide et dans certains cas, comme les variables membres const, c'est le seul moyen de les initialiser.

Il peut également y avoir une raison pour implémenter la composition à l'aide d'un pointeur. Par exemple, Bar pourrait être un type polymorphe et vous ne connaissez pas le type concret au moment de la compilation. Ou peut-être que vous souhaitez transmettre déclarer Bar pour minimiser les dépendances de compilation (voir idiome PIMPL ). Ou peut-être que la multiplicité de cette relation est de 1 à 0..1 et vous devez pouvoir avoir un _ Bar nul. Bien sûr, car il s'agit de Composition Foo devrait posséder le Bar et dans le monde moderne de C++ 11/C++ 14, nous préférons utiliser des pointeurs intelligents au lieu de posséder des pointeurs bruts:

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

J'ai utilisé std::unique_ptr ici car Foo est l'unique propriétaire de Bar mais vous voudrez peut-être utiliser std::shared_ptr si un autre objet a besoin d'un std::weak_ptr à Bar.

Association

L'association est généralement implémentée à l'aide d'un pointeur comme vous l'avez fait:

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

Bien sûr, vous devez être sûr que Bar sera vivant pendant que Foo l'utilisera sinon vous avez un pointeur pendant. Si la durée de vie de Bar est moins claire, alors un std::weak_ptrpeut être plus approprié:

// 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";
  }
};

Maintenant, Foo peut utiliser le Bar sans craindre de se retrouver avec un pointeur suspendu:

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

Dans certains cas, lorsque la propriété de Bar n'est vraiment pas claire, une association pourrait être implémentée à l'aide d'un std::shared_ptr mais je pense que cela devrait être un dernier recours.

Concernant votre implémentation de l'agrégation avec une copie complète d'un pointeur Bar. Je n'aurais pas dit que c'était une implémentation typique mais, comme je l'ai dit, cela dépend de vos besoins. Vous devez vous assurer d'appeler delete sur votre pointeur de membre bar dans le destructeur Foo, sinon vous avez une fuite de mémoire (ou utilisez un pointeur intelligent).

16
Chris Drew