I'm beginning to study OOAD and I'm having difficulty finding a C++
code example that'd illustrate how Association
, Aggregation
and Composition
are implemented programmatically. (There are several posts everywhere but they relate to C# or java). I did find an example or two, but they all conflict with my instructor's instructions and I'm confused.
My understanding is that in:
- Association: Foo has a pointer to Bar object as a data member
- Aggregation: Foo has a pointer to Bar object and data of Bar is deep copied in that pointer.
- Composition: Foo has a Bar object as data member.
And this is how I've implemented it:
//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;
}
};
Is this correct? If not, then how should it be done instead? It'd be appreciated if you also give me a reference of a code from a book (so that I can discuss with my instructor)
I'm going to ignore Aggregation. It is not a very clearly defined concept and in my opinion it causes more confusion than it is worth. Composition and Association are quite enough, Craig Larman told me so. It might not be the answer your instructor was looking for but it is unlikely to be implemented in C++ any differently to Association anyway.
There is not one way of implementing Composition and Association. How you implement them will depend on your requirements, for example the multiplicity of the relationship.
Composition
The simplest way of implementing composition is using a simple
Bar
member variable much like you suggested. The only change I would make is to initialize thebar
in the constructor member initializer list:It is generally a good idea to initialize member variables using the constructor initialization list, it can be quicker and in some cases like const member variables it is the only way to initialize them.
There might also be a reason to implement composition using a pointer. For example
Bar
could be a polymorphic type and you don't know the concrete type at compile time. Or perhaps you want to forward declareBar
to minimize compilation dependencies (see PIMPL idiom). Or perhaps the multiplicity of this relationship is 1 to 0..1 and you need to be able to have a nullBar
. Of course because this is CompositionFoo
should own theBar
and in the modern world of C++11/C++14 we prefer to use smart pointers instead of owning raw pointers:I've used
std::unique_ptr
here becauseFoo
is the sole owner ofBar
but you might want to usestd::shared_ptr
if some other object needs astd::weak_ptr
toBar
.Association
Association would usually be implemented using a pointer as you have done:
Of course you need to be confident that
Bar
will be alive whileFoo
is using it otherwise you have a dangling pointer. If the lifetime ofBar
is less clear then astd::weak_ptr
may be more appropriate:Now
Foo
can use theBar
without fear of being left with a dangling pointer:In some cases where ownership of
Bar
is really unclear an Association could be implemented using astd::shared_ptr
but I think that should be a last resort.Regarding your implementation of Aggregation with a deep copy of a
Bar
pointer. I wouldn't have said that was a typical implementation but, as I said, it depends on your requirements. You do need to make sure you calldelete
on yourbar
member pointer in theFoo
destructor though otherwise you have a memory leak (or use a smart pointer).