Search

Admin

10.2 — 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 composition (also known as object composition).

More specifically, composition is used for objects that have a has-a relationship to each other. A car has-a metal frame, has-an engine, and has-a transmission. A personal computer has-a CPU, a motherboard, and other components. You have-a head, a body, some limbs.

So far, all of the classes we have used in our examples have had member variables that are built-in data types (eg. int, double). While this is generally sufficient for designing and implementing small, simple classes, it quickly becomes burdensome for more complex classes, especially those built from many sub-parts. In order to facilitate the building of complex classes from simpler ones, C++ allows us to do object composition in a very simple way -- by using classes as member variables in other classes.

Lets take a look at some examples of how this is done. If we were designing a personal computer class, we might do it like this (assuming we’d already written a CPU, Motherboard, and RAM class):

Initializing class member variables

In the previous lesson on initializer lists, you learned that the preferred way to initialize class members is through initializer lists rather than assignment. So let’s write a constructor for our PersonalComputer class that uses an initialization list to initialize the member variables. This constructor will take 3 parameters: a CPU speed, a motherboard model, and a RAM size, which it will then pass to the respective member variables when they are constructed.

Now, when a PersonalComputer object is instantiated using this constructor, that PersonalComputer object will contain a CPU object initialized with nCPUSpeed, a Motherboard object initialized with strMotherboardModel, and a RAM object initialized with nRAMSize.

It is worth explicitly noting that composition implies ownership between the complex class and any subclasses. When the complex class is created, the subclasses are created. When the complex class is destroyed, the subclasses are similarly destroyed.

A full example

While the above example is useful in giving the general idea of how composition works, let’s do a full example that you can compile yourself. Many games and simulations have creatures or objects that move around a board, map, or screen. The 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.

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

Creature.h:

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

Why use composition?

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, using composition provides a number of useful benefits:

  1. Each individual class can be kept relatively simple and straightforward, focused on performing one task. This makes those classes easier to write and much easier to understand. For example, Point2D only worries about point-related stuff, which helps keep it simple.
  2. Each subobject 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 complex class can have the simple 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 complex object, because it can delegate tasks to the sub-objects, who already know how to do them. 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.

One question that new programmers often ask is “When should I use composition instead of direct implementation of a feature?”. There’s no 100% answer to that question. However, 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 (eg. Point2D), OR the coordination of subclasses (eg. Creature). 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 -- Constructor initialization lists

22 comments to 10.2 — Composition

  • jo

    public:
    Creature(std::string strName, const Point2D &cLocation)
    : m_strName(strName), m_cLocation(cLocation)
    {
    }

    m_cLocation(cLocation) calls copy constructor or assignment operator?
    m_cLocation and cLocation are already instantiated. thats why i asked so.

    Also when i add a copy constructor it says…………….
    class ‘Point2D’ : no copy constructor available………

  • jobin

    when i add a copy constructor it says…
    class ‘Point2D’ : no copy constructor available

  • 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 toturile is very usfule

  • J.D.

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

  • Insomniac

    Hi Alex, thanks for the great tutorial!
    Just a small remark:
    In the code above for main.cpp, line 9

    the “std::” part is obsolete (because of line 7)… just in case someone wonders.

  • include# “Cpu.h”
    include# “Motherboard.h”
    include# “Ram.h”

    class personal computer
    {
    private:
    Cpu m_cCpu;
    Motherboard m_cMotherboard;
    Ram m_cRam;
    };

    Personal Computer::Personal Computer (n_cCPUspeed, *str_cMOTHERBOARDModel, n_cRAMsize)
    :n_cCPU(cpuspeed), *str_motherboard(MOTHERBOARDMODEL), nRAM(Rramsize)

  • duetosideeffects

    first of all, love the tutorials: so amazing, informative, and best of all, it doesn’t baby us like some textbooks do.

    - Question: My question is why are Container classes declared the way they are, and why their default parameters are the same way.

    The real question is that I fail to see the logic behind why declaring a class within a class declaration is done with the class name and parentheses for the parameters only.
    -->(Position(1,3,4), Orientation(10, 90), “Fred”)
    &
    -->(EnemyDrone(const Position &cPos = Position(0,0,0), const Orientation &cOrient = Orientation(0,0), …)

    (Sorry about the length, but I thought it would be better to have more info then needed than less -- I also feel like I’m slowly answering my own question asking)

  • Martin

    Hi,
    I wonder why you created default constructor of Creature class and set it private. When you declare (and you declared) constructor with extra parameters ( Creature(std::string strName, const Point2D &cLocation) ) default constructor should not be available. Or it is ? Because when i create some constructor with parameters in my VS 2010 default one is not implicitli created by C++ and calling it causes compile error…

    Thanks

  • Dr. Jekyll

    To Alex,

    I thought that you would like to know that on this page the top left hyperlink and lower right bottom of page links that usually takes you back a page instead is a link to A.5 Debugging your program (watching variables and the call stack).

    From Dr.Jekyll

    P.s Have you thought about adding a preview comment function into the leave a reply box. This could seriously reduce the amount of people forgeting to use the appropriate tags. The one in the forum is great.

  • prady

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

  • jaykay

    Line 26 of Creature.h :

    out << cCreature.m_strName.c_str() << " is at " << cCreature.m_cLocation;

    Can someone explain why the c_str() function is used?

    Thanks

  • 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 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

  • Neha Nigam

    // We don’t want people to create Creatures with no name or location
        // so our default constructor is private
        Creature() { }

    do we really need this, as when we define parameterized constructor , compiler will not provide default one

  • ranjeet

    Hi Alex,

    In below sentence i think should use named "object of class" instead of "class", and s"ub-object of class" instead of subclass, this creates a confusion in reading, class can not be created nor destroyed , this is object.  
      
    "When the complex class is created, the subclasses are created. When the complex class is destroyed, the subclasses are similarly destroyed."

    Thanks.

  • Pofke1

    I was trying to create something like you, before i finished writing it, i encountered a problem (explained in code).
    Also when i execute “nname()” without “if” it works as expected.

    heres my creature.h:

    Output:
    press 1 to enter a new name
    press 2 to enter new cords
    press 3 to view info
    1
    enter a name for your creature:
    your creature name is:
    Press any key to continue . . .

    • Monil Vikam

      @Pofke1

      Try adding

      before getline, this will solve your problem
      This will clean your input basically "\n" which is residue after you enter "1\r\n"
      Currently getline is catching "\n" character from istream.

Leave a Comment

  

  

  

nineteen + two =

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