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 a 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 recipient, and can only be part of the recipient 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

92 comments to 10.2 — Composition

  • Abdul mannan

    Can we do composition if parent class is abstract?

  • boredprogrammer

    I think, a good thing to add to these parts of the tutorial are the UML diagrams where the ideas are extracted from. Just to post a picture of the UML diagram used to represent compositions, aggregations, etc. I think the direct connection from modelling a piece of software via UML to the implementation in ANY language, would make more evident the explained here. Ie, aggregations exists even when some lenguages don't use pointers or even references, but there are ways to have the same model of parts.

    Just an idea... and also, boxes, arrows, diagrams are nice!

  • Aniket

    You haven't answered the question "When should I use a subclass instead of direct implementation of a feature?"
    Instead, after that question you have been answering "Why should I use a subclass instead of direct implementation of a feature?"

    I want to understand that when part clearly.

    • Alex

      You should generally make a member a class type when the storage or manipulation of that member is non-trivial. For example, if a member needs to represent a point or a fraction, you should use a class, because the storage and manipulation of that data isn't trivial. If you need to store an employee's ID or age, use an int, because that is trivial.

  • Ritesh

    Hey Alex,
    I wrote the above program of creatures and point2d, by creating a Creature header and its cpp and same for Point2D I created both its header and cpp
    I defined the friend function and set functions in the cpp files. heres the code

    Point2d.h
    #pragma once
    #include<iostream>
    class Point2D
    {
        int m_x;
        int m_y;
    public:
        Point2D(int x = 0, int y = 0) :m_x(x), m_y(y)
        {

        }
        friend std::ostream& operator<<(std::ostream &out, const Point2D &obj);
        void setPoint(int x = 0, int y = 0);
    };

    Point2D.cpp
    #include "Point2D.h"
    std::ostream& operator<<(std::ostream &out, const Point2D &obj)
    {
        out << "(" << obj.m_x << "," << obj.m_y << ")\n";
        return out;
    }
    void Point2D::setPoint(int x, int y)
    {
        m_x = x;
        m_y = y;
    }

    Creature.h
    #pragma once
    #include<string>
    #include<iostream>
    #include "Point2D.h"
    class Creature
    {
        std::string m_name;
        Point2D m_location;
    public:
        Creature(const std::string &name, const Point2D &obj) :m_name(name), m_location(obj)
        {

        }
        friend std::ostream& operator<<(std::ostream &out, const Creature &obj);
        void moveTo(int x = 0, int y = 0);
    };

    Creature.cpp
    #include "Creature.h"
    std::ostream& operator<<(std::ostream &out, const Creature &obj)
    {
        out << obj.m_name << " is at " << obj.m_location<<'\n';
        return out;
    }
    void Creature::moveTo(int x, int y)
    {
        m_location.setPoint(x, y);
    }

    and Finally main.cpp
    #include "stdafx.h"
    #include<iostream>
    #include "Point2D.h"
    #include "Creature.h"
    using namespace std;
    int main()
    {
        cout << "Enter the name of your creature";
        string name;
        cin >> name;
        Creature creature(name, Point2D(4, 12));
        cout << creature; // this line gives linker error
        return 0;
    }
    and the linker error is LNK2019, unresolved externals referenced in main, and when I'll comment this line cout<< creature, then code compiles and links fine. I am not understanding what's going wrong in this code, why my linker is not linking the definition of the overloaded cout << creature;

    • Alex

      Not sure. When I put this all in one file, it compiled fine. Therefore, it appears like the issue here is that the linker is not able to find the definition of operator<< for Creature. That suggests to me that maybe you forgot to add Creature.cpp to your project, so it's not actually being compiled/linked when you compile your program.

  • What I learn from this section.

    Composition makes programmers less worry about the deletion of the object. In most of the case, this type of relation guarantee the existance of the member in such a way it automatically knows when to birth and to death.

    Some questions that I do not understand:

    What is the difference between a default constructor and a specific one? Why we need two here? What is the meaning of the default constructor? What kind of date type is m_x and m_y in the default constructor?

    What does it mean? If I don't want to use overload operator how to write this piece of code?

    • nascardriver

      Hi Ryder!

      > What is the difference between a default constructor and a specific one? Why we need two here?

      > What is the meaning of the default constructor?
      The default constructor gets called whenever you create an object without passing arguments to the constructor.

      > What kind of date type is m_x and m_y in the default constructor?
      The are the local variables @m_x and @m_y

      > What does it mean? If I don’t want to use overload operator how to write this piece of code?
      You can't do that without overloading the << operator.

  • Nur

    Hello Alex,
    Hope you are doing well.
    I have a question regarding to above given Creature class.
    My question:

    Can we consider internally (or separately) the relationship between Creature class's object and std::ostream's object std::cout as Dependencies, although you have given as a composition between Creature and Point2D classes?

    If what I said is correct then it is worth noting that the concepts of composition and dependencies are not mutually exclusive, and can be mixed freely within the same class.
    Am I right?. If not, please could you clarify it?

    Thanks for a great tutorial!I have never seen such a tutorial in C++. It is the most precious one.

    • Alex

      Yes, I'd say std::cout is a dependency in this case, as the class uses it temporarily. You're correct in that the various relationships aren't exclusive -- a single class might contain an composition element, an association element, and a dependency.

  • Nurlan

    Hello Alex,
    I hope you are fine.
    As you said to qualify as a composition, an object and a part must have the  following relationship:
    One of them is The part (member) can only belong to one object (class) at a time.

    And you gave the Fraction class  as a great example of a composition:

    class Fraction
    {
    private:
        int m_numerator;
        int m_denominator;

    public:
        Fraction(int numerator=0, int denominator=1):
        m_numerator(numerator), m_denomin1ator(denominator)
        {
        }
    };

    //Now, let's write the main() function by myself
    int main()
    {
      Fraction f1(3, 4);
      Fraction &f2=f1;//is this still composition since now part (members)belongs two //objects, which has the same address,i.e. f2 is reference to f1;

    }
    Is this composition even it is belongs to two objects which has the same address, or it is considered to one object since it is alias name of f1?
    Thanks in advance!

    • Alex

      Fraction is a composition since the parts (m_numerator and m_denominator) belong to the whole (the Fraction object).

      The fact that you have a second variable referencing the first variable is immaterial. f1 owns its members. f2 is just a reference to f1, and has no bearing on ownership.

  • Zoran

    In Creature constructor, why do you pass name by value, unlike location?

    That is, instead of

    wouldn't this be better:

    Is there a reason?

  • Omri

    "the heart is now owned by the donor, and can only be part of the donor object unless transferred again"
    Perhaps:
    "the heart is now owned by the receiver only, and can only be part of the receiver object (no more belongs to the doner, no mutual ownership) until transferred again"

  • Omri

    Typo:
    "For example, a heart is an part of a person’s body."
    =>
    "For example, a heart is *a* part of a person’s body."

  • Rohit

    Hi Alex!

    1) Suppose we create a .h file which contain function declarations and another .cpp file which contains      function definition. Now if we add .h file to our program, does it itself include .cpp file or we need to add .cpp file also and how?

    2) Is file handling discussed in these tutorials?

  • robbe

    I'm probably wrong but since a programmer has no way of knowing a Point2D object contains merely fundamental type member variables, shouldn't the m_location variable (of type Point2D) in the Creature class be dynamically allocated and destroyed according to RAII?

    • Alex

      No. Classes are expected to clean up after themselves. Point2D should clean up itself when it is destroyed regardless of how it is implemented. Any class using Point2D should not need to worry about this, regardless of whether the class is dynamically allocated or not.

  • Michiel

    In main.cpp you forgot to add std:: before the first cout

Leave a Comment

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