Search

10.3 — Aggregation

In the previous lesson on Composition, we noted that object composition is the process of creating complex objects from simpler ones. We also talked about one type of object composition, called composition. In a composition relationship, the whole object is responsible for the existence of the part.

In this lesson, we’ll take a look at the other subtype of object composition, called aggregation.

Aggregation

To qualify as an aggregation, a whole object and its parts must have the following relationship:

  • The part (member) is part of the object (class)
  • The part (member) can belong to more than one object (class) at a time
  • The part (member) does not have its existence managed by the object (class)
  • The part (member) does not know about the existence of the object (class)

Like a composition, an aggregation is still a part-whole relationship, where the parts are contained within the whole, and it is a unidirectional relationship. However, unlike a composition, parts can belong to more than one object at a time, and the whole object is not responsible for the existence and lifespan of the parts. When an aggregation is created, the aggregation is not responsible for creating the parts. When an aggregation is destroyed, the aggregation is not responsible for destroying the parts.

For example, consider the relationship between a person and their home address. In this example, for simplicity, we’ll say every person has an address. However, that address can belong to more than one person at a time: for example, to both you and your roommate or significant other. However, that address isn’t managed by the person -- the address probably existed before the person got there, and will exist after the person is gone. Additionally, a person knows what address they live at, but the addresses don’t know what people live there. Therefore, this is an aggregate relationship.

Alternatively, consider a car and an engine. A car engine is part of the car. And although the engine belongs to the car, it can belong to other things as well, like the person who owns the car. The car is not responsible for the creation or destruction of the engine. And while the car knows it has an engine (it has to in order to get anywhere) the engine doesn’t know it’s part of the car.

When it comes to modeling physical objects, the use of the term “destroyed” can be a little dicey. One might argue, “If a meteor fell out of the sky and crushed the car, wouldn’t the car parts all be destroyed too?” Yes, of course. But that’s the fault of the meteor. The important point is that the car is not responsible for destruction of its parts (but an external force might be).

We can say that aggregation models “has-a” relationships (a department has teachers, the car has an engine).

Similar to a composition, the parts of an aggregation can be singular or multiplicative.

Implementing aggregations

Because aggregations are similar to compositions in that they are both part-whole relationships, they are implemented almost identically, and the difference between them is mostly semantic. In a composition, we typically add our parts to the composition using normal member variables (or pointers where the allocation and deallocation process is handled by the composition class).

In an aggregation, we also add parts as member variables. However, these member variables are typically either references or pointers that are used to point at objects that have been created outside the scope of the class. Consequently, an aggregation usually either takes the objects it is going to point to as constructor parameters, or it begins empty and the subobjects are added later via access functions or operators.

Because these parts exist outside of the scope of the class, when the class is destroyed, the pointer or reference member variable will be destroyed (but not deleted). Consequently, the parts themselves will still exist.

Let’s take a look at a Teacher and Department example in more detail. In this example, we’re going to make a couple of simplifications: First, the department will only hold one teacher. Second, the teacher will be unaware of what department they’re part of.

In this case, bob is created independently of department, and then passed into department‘s constructor. When department is destroyed, the m_teacher reference is destroyed, but the teacher itself is not destroyed, so it still exists until it is independently destroyed later in main().

Pick the right relationship for what you’re modeling

Although it might seem a little silly in the above example that the Teacher’s don’t know what Department they’re working for, that may be totally fine in the context of a given program. When you’re determining what kind of relationship to implement, implement the simplest relationship that meets your needs, not the one that seems like it would fit best in a real-life context.

For example, if you’re writing a body shop simulator, you may want to implement a car and engine as an aggregation, so the engine can be removed and put on a shelf somewhere for later. However, if you’re writing a racing simulation, you may want to implement a car and an engine as a composition, since the engine will never exist outside of the car in that context.

Rule

Implement the simplest relationship type that meets the needs of your program, not what seems right in real-life.

Summarizing composition and aggregation

Compositions:

  • Typically use normal member variables
  • Can use pointer members if the class handles object allocation/deallocation itself
  • Responsible for creation/destruction of parts

Aggregations:

  • Typically use pointer or reference members that point to or reference objects that live outside the scope of the aggregate class
  • Not responsible for creating/destroying parts

It is worth noting that the concepts of composition and aggregation are not mutually exclusive, and can be mixed freely within the same class. It is entirely possible to write a class that is responsible for the creation/destruction of some parts but not others. For example, our Department class could have a name and a Teacher. The name would probably be added to the Department by composition, and would be created and destroyed with the Department. On the other hand, the Teacher would be added to the department by aggregation, and created/destroyed independently.

While aggregations can be extremely useful, they are also potentially more dangerous, because aggregations do not handle deallocation of their parts. Deallocations are left to an external party to do. If the external party no longer has a pointer or reference to the abandoned parts, or if it simply forgets to do the cleanup (assuming the class will handle that), then memory will be leaked.

For this reason, compositions should be favored over aggregations.

A few warnings/errata

For a variety of historical and contextual reasons, unlike a composition, the definition of an aggregation is not precise -- so you may see other reference material define it differently from the way we do. That’s fine, just be aware.

One final note: In the lesson Structs, we defined aggregate data types (such as structs and classes) as data types that group multiple variables together. You may also run across the term aggregate class in your C++ journeys, which is defined as a struct or class that has no provided constructors, destructors, or overloaded assignment, has all public members, and does not use inheritance -- essentially a plain-old-data struct. Despite the similarities in naming, aggregates and aggregation are different and should not be confused.

std::reference_wrapper

In the Department/Teacher example above, we used a reference in the Department to store the Teacher. This works fine if there is only one Teacher, but if there is a list of Teachers, say std::vector, we can’t use references anymore.

List elements cannot be references, because references have to be initialized and cannot be reassigned. Instead of references, we could use pointers, but that would open the possibility to store or pass null pointers. In the Department/Teacher example, we don’t want to allow null pointers. To solve this, there’s std::reference_wrapper.

Essentially, std::reference_wrapper is a class that acts like a reference, but also allows assignment and copying, so it’s compatible with lists like std::vector.

The good news is that you don’t really need to understand how it works to use it. All you need to know are three things:
1) std::reference_wrapper lives in the <functional> header.
2) When you create your std::reference_wrapper wrapped object, the object can’t be an anonymous object (since anonymous objects have expression scope would leave the reference dangling).
3) When you want to get your object back out of std::reference_wrapper, you use the get() member function.

Here’s an example using std::reference_wrapper in a std::vector:

To create a vector of const references, we’d have to add const before the std::string like so

If this seems a bit obtuse or obscure at this point (especially the nested types), come back to it later after we’ve covered template classes and you’ll likely find it more understandable.

Quiz time

Question #1


Would you be more likely to implement the following as a composition or an aggregation?
a) A ball that has a color
b) An employer that is employing multiple people
c) The departments in a university
d) Your age
e) A bag of marbles

Show Solution

Question #2


Update the Department/Teacher example so the Department can handle multiple Teachers. The following code should execute:

This should print:

Department: Bob Frank Beth
Bob still exists!
Frank still exists!
Beth still exists!

Show Hint

Show Solution


10.4 -- Association
Index
10.2 -- Composition

167 comments to 10.3 — Aggregation

  • Keith C

    Hello,
    I would like to understand this behavior better, I added chaining the add calls, i.e.

    when I defined add as below, it failed with the vector only holding the first (non-chained) add.
    I added a print statement to the Department constructor, and it was only called once.

    replacing the return type to be Department&, caused the vector to hold all 3.

    As I typed this comment, I also tried auto &, and that worked.

    So was there was some sort of "ghost" department that was returned on the stack ? That was never instanciated...
    Thanks in advance for your thoughts.
    -Keith

    • nascardriver

      Constructors can be elided for optimization reasons. There are also many different kinds of special constructors, which have haven't covered all at this point. Constructors are not a reliable source of saying what happened. Your first version is identical to

      When you return, a new `Department` is created.

  • Minor typo in the opening: "In the previous lesson on Composition, we noted that object composition is the process of creating complex objects from simpler one[s]." - needs an "s"

    And under "A few warnings": "One final note: In the lesson Structs, we defined aggregate data types (such as structs and classes) as data types that group[s] multiple variables together." - doesn't need its "s"

    Thank you for these tutorials! C++ feels really strange when you're used to Python, but it's starting to make sense.

  • Ayrton Fithiadi Sedjati

    In the quiz no. 2, you implemented the add function using push_back(). Do you think the following code should be semantically equivalent?

    First we resize our vector to be 1-length longer to hold the new teacher, then we assign the new teacher into the extra length we just added. So why do I get the following error?

    "Error    C2512    'std::reference_wrapper<const Teacher>::reference_wrapper': no appropriate default constructor available"

    I have double checked the rest of my code such that if I use your implementation of the add function, my code works as expected.

    • nascardriver

      `resize` has to insert an element into the empty slots it creates. To create the element, it tries to default-construct a `reference_wrapper`, but a `reference_wrapper` cannot be created without an object to reference (References have to reference something), so it fails.
      `push_back` doesn't need to create an empty `reference_wrapper`, because we told it what the new element will be.

  • Sam

    Here is my solution for problem 2.

    Also, in the solution, why do you return the teachers name (inside getName()) by const reference? (const std::string&)

    • nascardriver

      There's no reason to copy the name every time `getName` gets called. Copying data is slow and should be avoided. By returning a reference, we avoid the copy.
      In your line 27, " " is a single character in a string. Strings are expensive. Since you only have 1 character, use single quotation marks ' '.

  • AbraxasKnister

    I don't see what the dynamic allocation adds to the example. It's fully ok to create the Teacher variables by normal invocation of the constructor and then pass their address to the Department::add():

    I would also say that the Department::add() function could be built just as well using (non const) references, avoiding that the user passes in an array of Teachers. But I see that this is just a matter of preference--that way you tell the user that his variable needs already exist, ie passing in a temporary object would cause issues.

    My solution:

    For some reason the range based variant of the loop doesn't work. Why is that? (More precisely it adds the last constituent of tt).

    • nascardriver

      > I don't see what the dynamic allocation adds to the example.
      Agreed, marked for an update.

      > Department::add() function could be built just as well using (non const) references
      It should. The problem aren't arrays, but the possibility to pass a `nullptr`. Also noted.

      > otherwise line 63 wouldn't work
      The quiz is wrong too. It should be a `const std::string&` (For you, `const name_t&`). I suppose this quiz is very old.
      Your code didn't work because your line 63 uses wrong braces. The inner braces try to initialize the first element (A `Teacher`) of the vector. A `Teacher` is initialized by a `std::string`, but there is not `std::string` constructor that takes a list of `const char*`. You need braces around each `Teacher`:

      > the range based variant of the loop doesn't work
      Never pass/loop class-types by value unless they're guaranteed cheap to copy. In your range-based loop, `i` is a copy of the current teacher. `i` dies at the end of each loop's iteration, but you're storing a reference in `d` (Bad names, don't use abbreviations). Loop over references

      Thanks for pointing out the old code! If you find more, please point them out. It's likely that you find more. The further you get into the lessons, the fewer people have read them and helped improve them.

      • AbraxasKnister

        Thanks for the quick response. I'll make as much remarks as I can.

        > Never pass ...

        Noted! How stupid of me. 10.4 also uses dynamic allocation btw.

        What is wrong with

        ie one brace (easier to type)?

        • nascardriver

          You can do that too if you like to. I prefer it when it's obvious that I'm calling a constructor. That way it's also easier to update the vector creation if I ever add a parameter to the `Teacher` constructor.

    • nascardriver

      For the update, I stole the `std::reference_wrapper` section from chapter 12. Please read the new `std::reference_wrapper` section and quiz 2 in this lesson, as otherwise you'd miss the introduction of `std::reference_wrapper`.

  • Ayoub

    I think ,at the example of department and teacher, the department must be created first then the teacher; cuz department knows about the existence of the teacher, and department may hold many teachers.
    Also the definition of department class and teacher class must also be changed.

  • Habiba

    i am combining the two codes above i.e. the one for single Teacher type pointer and the one with updation in Department class due to addition of add(Teacher *teacher) functon

    #include <string>
    using namespace std;
    #include <iostream>

    class Teacher
    {
    private:
        string m_name;

    public:
        Teacher(string name)
            : m_name(name)
        {
        }

        string getName() { return m_name; }
    };

    class Department
    {
    private:
        Teacher *m_teacher;// This dept holds only one teacher for simplicity, but it could hold many teachers

    public:
        Department()
        {
            cout<<"Hello"<<endl;
        }
        int add(Teacher *teacher)
        {
            m_teacher=teacher<<endl;
        }
    };

    int main()
    {
        // Create a teacher outside the scope of the Department
        Teacher *teacher1 = new Teacher("Bob"); // create a teacher
        Teacher *teacher2 = new Teacher("Henry"); // create a teacher
        Teacher *teacher3 = new Teacher("Sara"); // create a teacher
        
            // Create a department and use the constructor parameter to pass
            // the teacher to it.
            Department dept;
            dept.add(teacher1);
            dept.add(teacher2);
            dept.add(teacher3);

        // dept goes out of scope here and is destroyed

        // Teacher still exists here because dept did not delete m_teacher

        cout << teacher1->getName() <<teacher2->getName() <<teacher3->getName() << " still exists!";
        delete teacher1;
        delete teacher2;
        delete teacher3;

        return 0;
    }

  • Imran

    Can u plz Make a program in which object of class employee and object of class students are attributes of class Manager and class scientist and object of class employee is an attribute of class labourer

  • Anastasia

    Thanks for the lesson, I think it's very well explained, but I have hard time grasping abstract concepts :/

    I'm writing a card game and there are 2 classes: 'Card' and 'Deck'. Which kind of relationship they have? It seems somewhat similar to the 1e question of the quiz (a bag of marbles), so I'd think it's aggregation, but then again there are some composition-like points too...

    Aggregation:
    - a card(s) can exist independently of a deck

    Composition:
    - a deck can't exist without cards (intrinsic property?)
    - a card can't belong to more than one deck at a time

    I don't know whether a deck should be responsible for destroying cards... I'd say yes, but only because it seems easier to manage memory this way (allocate memory for the deck (eg. Card array) and then 'delete' it as a whole, rather than manage each card separately). Abstract thinking is not at all my thing...(better don't ask me whether a card is aware of being part of a deck lol)
    Is it very important to grasp the difference between composition and aggregation?

    • Alex

      One thing that's maybe not as clear as it could be is that some relationships can be implemented using more than one technique. A Deck could be implemented as a composition or an aggregation, depending on whether Cards do or don't need to exist independently of the Deck itself. I'd favor composition over aggregation if it meets your requirements.

      If this stuff doesn't gel with you, I'd move on.

      • Anastasia

        Implementing it as a composition seemed like a better choice to me too, but I wasn't sure (esp. after getting to the question with bag of marbles). Thank you for the reply!

  • noobmaster

    I still don't really get it.

    we can just use normal member variable here and ofc Bob still exist because we declared it outside the scope of the Department. So what is the benefit of using reference/pointer? For scope things? So we can use them in multiple functions (bcs they don't get destroyed until we explicitly delete them)?

Leave a Comment

Put all code inside code tags: [code]your code here[/code]