Search

10.2 — Composition

Object composition

In real-life, complex objects are often built from smaller, simpler objects. For example, a car is built using a metal frame, an engine, some tires, a transmission, a steering wheel, and a large number of other parts. A personal computer is built from a CPU, a motherboard, some memory, etc… Even you are built from smaller parts: you have a head, a body, some legs, arms, and so on. This process of building complex objects from simpler ones is called object composition.

Broadly speaking, object composition models a “has-a” relationship between two objects. A car “has-a” transmission. Your computer “has-a” CPU. You “have-a” heart. The complex object is sometimes called the whole, or the parent. The simpler object is often called the part, child, or component.

In C++, you’ve already seen that structs and classes can have data members of various types (such as fundamental types or other classes). When we build classes with data members, we’re essentially constructing a complex object from simpler parts, which is object composition. For this reason, structs and classes are sometimes referred to as composite types.

Object Composition is useful in a C++ context because it allows us to create complex classes by combining simpler, more easily manageable parts. This reduces complexity, and allows us to write code faster and with less errors because we can reuse code that has already been written, tested, and verified as working.

Types of object composition

There are two basic subtypes of object composition: composition and aggregation. We’ll examine composition in this lesson, and aggregation in the next.

A note on terminology: the term “composition” is often used to refer to both composition and aggregation, not just to the composition subtype. In this tutorial, we’ll use the term “object composition” when we’re referring to both, and “composition” when we’re referring specifically to the composition subtype.

Composition

To qualify as a composition, an object and a part must have the following relationship:

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

A good real-life example of a composition is the relationship between a person’s body and a heart. Let’s examine these in more detail.

Composition relationships are part-whole relationships where the part must constitute part of the whole object. For example, a heart is an part of a person’s body. The part in a composition can only be part of one object at a time. A heart that is part of one person’s body can not be part of someone else’s body at the same time.

In a composition relationship, the object is responsible for the existence of the parts. Most often, this means the part is created when the object is created, and destroyed when the object is destroyed. But more broadly, it means the object manages the part’s lifetime in such a way that the user of the object does not need to get involved. For example, when a body is created, the heart is created too. When a person’s body is destroyed, their heart is destroyed too. Because of this, composition is sometimes called a “death relationship”.

And finally, the part doesn’t know about the existence of the whole. Your heart operates blissfully unaware that it is part of a larger structure. We call this a unidirectional relationship, because the body knows about the heart, but not the other way around.

Note that composition has nothing to say about the transferability of parts. A heart can be transplanted from one body to another. However, even after being transplanted, it still meets the requirements for a composition (the heart is now owned by the donor, and can only be part of the donor object unless transferred again).

Our ubiquitous Fraction class is a great example of a composition:

This class has two data members: a numerator and a denominator. The numerator and denominator are part of the Fraction (contained within it). They can not belong to more than one Fraction at a time. The numerator and denominator don’t know they are part of a Fraction, they just hold integers. When a Fraction instance is created, the numerator and denominator are created. When the fraction instance is destroyed, the numerator and denominator are destroyed as well.

While object composition models has-a type relationships (a body has-a heart, a fraction has-a denominator), we can be more precise and say that composition models “part-of” relationships (a heart is part-of a body, a numerator is part of a fraction). Composition is often used to model physical relationships, where one object is physically contained inside another.

The parts of a composition can be singular or multiplicative -- for example, a heart is a singular part of the body, but a body contains 10 fingers (which could be modeled as an array).

Implementing compositions

Compositions are one of the easiest relationship types to implement in C++. They are typically created as structs or classes with normal data members. Because these data members exist directly as part of the struct/class, their lifetimes are bound to that of the class instance itself.

Compositions that need to do dynamic allocation or deallocation may be implemented using pointer data members. In this case, the composition should be responsible for doing all necessary memory management itself.

In general, if you can design a class using composition, you should design a class using composition. Classes designed using composition are straightforward, flexible, and robust (in that they clean up after themselves nicely).

More examples

Many games and simulations have creatures or objects that move around a board, map, or screen. One thing that all of these creatures/objects have in common is that they all have a location. In this example, we are going to create a creature class that uses a point class to hold the creature’s location.

First, let’s design the point class. Our creature is going to live in a 2d world, so our point class will have 2 dimensions, X and Y. We will assume the world is made up of discrete squares, so these dimensions will always be integers.

Point2D.h:

Note that because we’ve implemented all of our functions in the header file (for the sake of keeping the example concise), there is no Point2D.cpp.

This Point2d class is a composition of its parts: location values x and y are part-of Point2D, and their lifespan is tied to that of a given Point2D instance.

Now let’s design our Creature. Our Creature is going to have a few properties: a name, which will be a string, and a location, which will be our Point2D class.

Creature.h:

This Creature is also a composition of its parts. The creature’s name and location have one parent, and their lifetime is tied to that of the Creature they are part of.

And finally, Main.cpp:

Here’s a transcript of this code being run:

Enter a name for your creature: Marvin
Marvin is at (4, 7)
Enter new X location for creature (-1 to quit): 6
Enter new Y location for creature (-1 to quit): 12
Marvin is at (6, 12)
Enter new X location for creature (-1 to quit): 3
Enter new Y location for creature (-1 to quit): 2
Marvin is at (3, 2)
Enter new X location for creature (-1 to quit): -1

Variants on the composition theme

Although most compositions directly create their parts when the composition is created and directly destroy their parts when the composition is destroyed, there are some variations of composition that bend these rules a bit.

For example:

  • A composition may defer creation of some parts until they are needed. For example, a string class may not create a dynamic array of characters until the user assigns the string some data to hold.
  • A composition may opt to use a part that has been given to it as input rather than create the part itself.
  • A composition may delegate destruction of its parts to some other object (e.g. to a garbage collection routine).

The key point here is that the composition should manage its parts without the user of the composition needing to manage anything.

Composition and subclasses

One question that new programmers often ask when it comes to object composition is, “When should I use a subclass instead of direct implementation of a feature?”. For example, instead of using the Point2D class to implement the Creature’s location, we could have instead just added 2 integers to the Creature class and written code in the Creature class to handle the positioning. However, making Point2D its own class has a number of benefits:

  1. Each individual class can be kept relatively simple and straightforward, focused on performing one task well. This makes those classes easier to write and much easier to understand, as they are more focused. For example, Point2D only worries about point-related stuff, which helps keep it simple.
  2. Each subclass can be self-contained, which makes them reusable. For example, we could reuse our Point2D class in a completely different application. Or if our creature ever needed another point (for example, a destination it was trying to get to), we can simply add another Point2D member variable.
  3. The parent class can have the subclasses do most of the hard work, and instead focus on coordinating the data flow between the subclasses. This helps lower the overall complexity of the parent object, because it can delegate tasks to its children, who already know how to do those tasks. For example, when we move our Creature, it delegates that task to the Point class, which already understands how to set a point. Thus, the Creature class does not have to worry about how such things would be implemented.

A good rule of thumb is that each class should be built to accomplish a single task. That task should either be the storage and manipulation of some kind of data (e.g. Point2D, std::string), OR the coordination of subclasses (e.g. Creature). Ideally not both.

In this case of our example, it makes sense that Creature shouldn’t have to worry about how Points are implemented, or how the name is being stored. Creature’s job isn’t to know those intimate details. Creature’s job is to worry about how to coordinate the data flow and ensure that each of the subclasses knows what it is supposed to do. It’s up to the individual subclasses to worry about how they will do it.

10.3 -- Aggregation
Index
10.1 -- Object relationships

68 comments to 10.2 — Composition

  • Kinten

    When you create the Overloaded operator, it calls GetX() and GetY(), but these aren’t defined yet so it could’t use them, I am right?

    • You should actually be okay here since this will all be declared in a class declaration. Unlike normal code files, the order that you include functions in class declarations doesn’t matter.

  • thank you very mutch

    thise tutorial is very usfule

  • This tutorial is very clear and useful.

  • J.D.

    Hey,
    Thank you for the tutorial. It helped a lot because my book didn’t go into very much detail about composition.

  • prady

    then what is Association ? if this chapter has details for association like composition and aggregation it will be much help full for all. 🙂

  • Patra

    Hi,
    Thanks for the tutorial, really informative.

    When I run this program, Point2D object is created only once, but Point2D destructor is called 2 times. Can you please help in understanding this?

    Since the constructor is called once but how can the destructed called twice?

    -Patra

    • Alex

      Good question. Here’s what happens: First, the Point2D constructor is called when we construct the initial Point2D(4, 7). This is then passed to the Creature constructor by const reference, and from there used to initialize m_location. This initialization happens via the implicit copy constructor provided by the compiler (which is the second constructor call that you’re missing -- if you explicitly define a copy constructor for class Point2D, you’ll see it get called at this point).

      The temporary Point2D(4,7) then gets destructed, and later on, when the Creature is destroyed, m_location calls the destructor.

  • Alex Bieniek

    In this lesson, you make your object classes entirely using header files. I don’t get it, I thought you couldn’t do much else in header files, outside of prototyping functions. If you can actually initialize your variables, implement functions, etc. what’s the purpose of even having a .cpp file that goes along with your .h file when you’re making objects?

    • C++ Newbie

      He did it to be concise in the example.  In reality, every function and constructor would just have a semicolon at the end of it, and the .cpp file would define everything

      • Alex

        The advantage of putting your code in a .cpp file is that it gets compiled once no matter how many times you include the header. Any function you put in a header gets re-compiled every time you include the header, which makes your compiling take longer and bloats your code.

  • Hello Alex, a few questions:

    1. In the Creature.h example:

    Removing c_str() doesn’t affect the class and whole program compiles fine. Well, that is not the problem. The program compiles and runs with this (c_str()), is actually a problem to me. After doing a few search I came to know that we use c_str() when we pass an std::string argument and called function expects a const char* (null-terminated C-Style constant string). But I am still unable to see any importance of this here. It is everywhere std::string. Can you tell why you have used c_str().

    2. Putting the input statements for nX and nY inside a do while at least won’t result in an infinite loop if I enter ‘p’. Why you kept everything in a while? Is there a reason?

    • Alex

      1) The c_str() isn’t needed. I updated the example to remove it.

      2) Ideally, we’d want to add error handling for bad input -- it’s omitted from these examples because it’s not relevant to what I’m trying to teach. You can use a do-while or even a for loop if you want.

  • Help me Alex. I first tried to get answer from overflow, but I think question is very stupid (downvotes 🙁 ):

    My question could be better understood by an example. Suppose, I have to write an application that is designed (only) to take data from user (e.g. name, address etc.) and print them back to the console after submission. My complex class is "User" and I would like to use composition here. Address and Qualification details are candidates for designing sub-classes for the complex (User) class, so the tree would look like;

    User variables…

    User(complex) => User_Address(subclass 1)

    User(complex) => User_Qualification(subclass 2)

    User variables…

    There are two functions in each sub-classe and both are given public access. For sub-class User_Address, functions would be set_address() and print_address(), same with qualification sub-class (member variables like user_pin, user_contact, user_highschool etc. are private). The complex class has some private member variables like user_name, user_age etc. The complex class too has two functions set_user() to take input, and print_user() to output user data. Complex class calls its sub-classes User_Address and User_Qualification for inputting and outputting user’s address and qualification details respectively. Now, my question is, is that good coding style. I have to give public access to my sub-class functions because "User" needs to call them (or otherwise make the User class a friend of its sub-classes). Giving public-access to functions means any function/class can call it and may inadvertently change the inner parts, that was not desired. So, is there a better way to do what I am trying here.

    • Alex

      It’s fine to make public access functions for both your User class and your Address and Qualification subclasses.

      Don’t worry to so much about who has access to your accessor functions -- worry more about making sure things are properly encapsulated, so that if you ever change the internal implementation of a class, you don’t have to change every program in existence that uses it.

  • Quang

    I have a few questions needed to be cleared:
    - In previous lessons, you said that if we dont know what exactly the user gonna type so we use dynamic memory allocation. So why didnt you use it in the example ?
    - Is it better to use getline() instead of cin cuz they might include spacebar?
    - How can you do this: while (1) {//code}
    - Assume the Creature() in Creature.h is put in public, can you give me an example how’s that gonna affect our code
    - What if I want to creat a game of chess where there are pawns, knight, rocks, bishop, ….and so is the location and their movement. Will i need to build enum classes 1st??
    Thank you for your time Alex!!

    • Alex

      > In previous lessons, you said that if we dont know what exactly the user gonna type so we use dynamic memory allocation. So why didnt you use it in the example ?

      I’m not sure where I would have used it. We only need to use dynamic memory allocation if there is a need to allocate a certain amount of memory at runtime. I don’t see any cases in this example that require that.

      > Is it better to use getline() instead of cin cuz they might include spacebar?

      It’s better to use whichever fits the needs of your program. If you want the user to be able to enter spaces, then getline() is probably the better choice.

      > How can you do this: while (1) {//code}

      while(1) always evaluates to true, so this is an infinite loop (we use break statements to escape from it).

      > Assume the Creature() in Creature.h is put in public, can you give me an example how’s that gonna affect our code

      You could do something like this:

      By making the default constructor private, you’re forcing people to provide a name and location for the Creature.

      > What if I want to creat a game of chess where there are pawns, knight, rocks, bishop, ….and so is the location and their movement. Will i need to build enum classes 1st??

      That’s probably a good way to go. I’d probably define an enum for the chess piece types (pawn, knight, rook). Because the movement types are highly correlated with the piece types, I’d probably use the piece enum to drive the movement logic as well.

      • Quang

        >I’m not sure where I would have used it. We only need to use dynamic memory allocation if there is a need to allocate a certain amount of memory at runtime. I don’t see any cases in this example that require that.
        It was in 6.9 — Dynamic memory allocation with new and delete, you said: "For example, we may want to use a string to hold someone’s name, but we do not know how long their name is until they enter it" and thats the reason to use dynamic memory allocation. Is it correct?

        • Devashish

          Quang,
          by saying “we may want to use a string to hold someone’s name, but we do not know how long their name is until they enter it”, Alex doesn’t mean that if we don’t know what exactly the user gonna type so we use dynamic memory allocation. Suppose your program has to be designed to take user’s name as input, perform some kind of operation on it and return something based on the calculation. Here you may need some dynamic memory to be allocated, because you don’t know the exact number of characters user’s name can have. There are some other reasons why you may want to do dynamic memory allocation in this case.

          As for the example above, there is no need to dynamically allocate memory. I guess you are thinking that why Alex doesn’t use any dynamic memory to store the value of std::string cName. As far as I know, the C style string, where we define a string as an array of characters and need to dynamically allocate the array if we don’t know what would be the exact size, is an alternative to std::string. It has been previously mentioned that if there is no special reason, we should use std::string instead of C-style string because std::string is safer to use, is flexible and also an easier way to handle strings. Second, Alex said that working with dynamic memory adds up extra overhead and increases the complexity of the program. It’s also dangerous if you don’t handle dynamic memory allocation and deallocation in a sensible way. That’s why we should save ourselves from using dynamically allocated memories if there is a better option available.

  • Quang

    Thank you guys so much! But what will happen if I want to use char instead of string, will I have to use dynamic allocation?

    • Devashish

      If you wish, you can use dynamic allocation but that’s not strictly necessary. If you are sure that creature names won’t cross a certain number of characters, you can use a fixed char array of that size. But if you use dynamic allocation, make sure you deallocate the dynamically allocated memory otherwise it’ll cause a memory leak.

  • Jason

    Hi Alex,

    Just wanted to point out, as Martin did above, that it’s unnecessary to make a private default constructor because making a non-default constructor causes the compiler not to automatically create a default constructor.

    Also, in your Creature.h file you have declared the friend operator<< method and also defined its implementation. Wouldn't there be no need for the modifier "friend" if its body is inside the class?

    • Alex

      You’re right, explicitly declaring a private default constructor is extraneous if you have a non-default constructor available.

      There are three ways to implement binary operators such as operator<<: 1) As a friend member function, as we've shown above. 2) As a non-friend member function, where the left-hand operand is the class object. In this case, the left-hand-operand is of type ostream&, and you can't add an overloaded operator<< to ostream, so this won't work. 3) As a non-friend non-member function. This will work. So yes, you can make this function a non-friend as long as you move it out of the class.

  • Rahul

    In Main.cpp

    Here Point2D(4,7) constructor is called right. How memory is allocated here without ‘new’ keyword, where it is allocated, what is it scope, when it is destroyed.

    Please help me know the source to fill the knowledge gap.

  • Mr D

    Hi Alex,

    Could you explain why you’re making the overloaded output operator a friend? I thought this was only necessary if the actual function (implementation) was outside of the class body, which here is not the case.

    In lesson 9.3 you did the same thing, but then you had the implementation of the overloaded output operator outside of the class definition, hence the need to make it a friend of the class.

    • Alex

      Yes. Because the left-hand parameter is an ostream, if the overloaded operator<< weren't a friend, it would have to be implemented inside the ostream class. But we don't have access to edit ostream. Making it a friend allows us to keep the left hand operand as an ostream object while defining the function outside of the ostream class.

  • Alex Silva

    Hi Alex,

    Your answer for Mr D’s question is not clear to me. According to lesson 8.13, the friend keyword should be used to access private members outside the class. Could you explicit this? What are the private members? Who access them?

    • Alex

      The private members of the class are any member variables (or functions) declared underneath a private (or protected) access specifier. The friend function can access them. If you re-review lesson 8.13, the Humidity and Temperature examples are instructive here, as the PrintWeather() function is able to directly access the private temp and humidity variables of the classes.

      With overloaded operators, the operator being overloaded normally must belong to the class of the left operand. However, if the left-operand is a class we don’t have access to modify (e.g. ostream) or isn’t a class at all (e.g. int), then we can make the overloaded operator a friend. Overloaded operators that are friends don’t need to be defined inside the class of the left operand.

      So, in this sense, the friend keyword serves two different but useful purposes.

  • Jess

    Hi, Alex! I love your tutorials - they are really detailed and informative. I have a question regarding some of the code in Creature.h (Lines 19-22):

    Why must Point2D &cLocation be passed in as a const? When I remove the const the code doesn’t compile.

    Thank you! (And apologies in advance if this is a silly question.)

    • Alex

      I think the problem here may be related to how we’re constructing our Creature:

      Note that Point2D(4, 7) is an anonymous variable that exists just within the expression. Anonymous objects can only be passed by value or const reference.

  • tata

    Hi, could someone confirm the following:

    1) m_cLocation(cLocation) is calling the (default) copy constructor of Point2D; this is so because Const or not, m_cLocation is being created at the time of the initialization

    2) the above would require a user-defined copy constructor for Point2D if Point2D contains dynamically allocated members

    3) the following is valid:
    Cents cMark(5); // would call Cents constructor
    Cents cNancy(cMark); // would call Cents copy constructor

    4) Is the following valid:
    Cents cMark(5); // would call Cents constructor
    Cents cNancy;
    cNancy(cMark); // would this call Cents assignment constructor instead?

    Thanks
    ------------
    (Below is a related question relating to the timing of member creation, please disregard if it’s inappropriate for this section)

    5) Is the following correct:

    if a class has a static member such as - static int nValue;

    nValue would not be created until a line (after and outside the class definition) such as - int className::nValue = 3;?

  • Kiran C K

    Inside Creature.h, in the line:

    1. why can not we use a non const reference to Point2D object?
    2. If the Point2D class contain any pointer members, logically, we can not use initializer list, right? or is there any another way?
    3. In the main file, aren’t we supposed to include the Point2D header file to pass an anonymous object as a parameter as it is declared nowhere inside main.cpp?

    • Alex

      1) You can’t pass const or anonymous objects to a non-const reference. Making the location parameter const allows more flexibility in passing arguments.
      2) Classes with pointers can still use initializer lists. You can set your member point to null in the list, or have the constructor itself allocate memory:

      e.g.

      3) Yes. Good catch. I’ve fixed the example.

  • Kiran C K

    Inside main.cpp, in the code:

    CodeBlocks compiles fine, if cCreature passes anonymous object as the second parameter. But if we create a object and pass it, as if we call constructor with parameter, the error

    shows up. Here pnt is the object of Point2D. Why does that happen?

    Note: It works fine, if an object is instantiated and passed it as a parameter in two separate lines, but it shows the above error when it is done in a single line, i.e., within the Creature object as parameter

    • Kiran C K

      Just an update on the error:

      the error is:

      in the line, if it was as below:

    • Alex

      Can you copy in the code that’s not working so we can see? Are you trying something like this?

      This won’t work -- you can’t define a named variable inside an expression.

  • Kiran C K

    Hi Alex,
    I also found that there is a third category of relationship within classes, known as association, in which, sub objects of both the classes are related but independent themselves. Could you also include an example for that?

    • Alex

      In an aggregation, a subobject can only belong to one object at a time. For example, a Teacher can only belong to one Department at a time. But the Department does not own the teachers.

      In an association, a subobject can belong to multiple objects at a time. For example, a Student can have multiple Teachers.

      As you can see, they’re quite related.

      When I rewrite the lesson, I’ll differentiate the two more clearly.

  • nick

    Hi Alex :
    in class Creature
    Initializing constructor is necessary

    If we change the code

    What is the reason for the error ?

    • Alex

      What error are you getting? Because neither m_strName nor m_cLocation require initializers, doing an assignment instead of an initialization should compile.

  • nick

    you are right ;
    Note that because we’ve implemented all of our functions in the header file (for the sake of keeping the example concise), there is no Point2D.cpp.

    the problem for the #include

  • Lokesh

    In Creature.h, the default constructor is redundant and not needed.

  • Hamza

    Hey  
    can you plz tell me the order of destruction in composition.
    It is very difficult for me to guess the destruction order in dry runs..

  • Tytoo

    Hi there, I have a question here. Suppose we do this,

    Point2D p (4 , 7);
    Creature cCreature ( cName, p );

    Since destroying cCreature will not destroy p, does this still counts as composition?

  • Nyap

    how could you actually get the specs of a computer
    is there something in the STL which allows for this

    • Alex

      Not that I’m aware of. I think you’ll have to use OS-level function calls for this. For example, on Windows, you can use OS function call getSystemInfo() (see here. How to do this will vary by OS.

  • Anddo

    I feel this is like great tutorial !! Even though it’s simpler to understand the logic of it faster and more than previous ones. Maybe because it focuses more on "how to think of doing things" instead of "how to do things". Just wanted to say thank you great teacher Alex 🙂

  • Am

    Hi Alex,

    First thanks for the great tutorial.
    would you please add some UML notation to section 10?

    Best

  • Matt

    There’s a spelling error in the first paragraph after the Point2D class… "thi" should be "the".

  • Matt

    In your main.cpp code, you used "using namespace std" in order to use "cout", "cin", and "endl" in their unqualified forms. But I noticed that you still used "std::string" rather than just "string".

    Also, in your Fraction class example, you have the member variables initialized, and then you also give them the same default parameter values in the constructor.

    • Alex

      I’ve updated the example to remove the using namespace std. I’ve also removed the unnecessary non-static member initializers. Thanks for pointing those out.

Leave a Comment

Put C++ code inside [code][/code] tags to use the syntax highlighter