Search code examples
c++ooppointerscomposition

Composition with a pointer


UML diagram of classes

I am having trouble with RobotControl class members. The UML specifies the relation between RobotControl’s position and RangeSensor as composition. Doesn't using pointers for them, make them aggregation? How should I declare - create these members with respect to UML, or has UML mistaken?


Solution

  • Pointers in C++ can be used for both aggregation and composition. The distinction is, as correctly noted by Douglas, whether the lifetime of the objects is interconnected. In other words: Is the child destroyed when the parent is destroyed? The answer Yes stands for composition, No for aggregation.

    How do we distinguish these cases in a C++ code?

    Pointers in C++ can mean the ownership of another (dynamically created) object, or just refer to an object owned by someone else. Let’s show the differences in examples. I’ll explain why pointers can be useful for each type of relationship.

    Aggregation with a pointer

    In this case, it is fine to only forward-declare the class Child before class Parent declaration and the child member can be set and re-set during the lifetime of the Parent.

    class Child;
    
    class Parent
    {
    public:
        Parent(Child* ch) : child(ch) {}
        Parent() : child(NULL) {}
        void setChild(Child* ch) { child = ch; }
    private:
        Child* child;
    };
    

    Composition with a pointer

    The longest example shows that we can dynamically create and destroy the child using a pointer. The child’s lifetime is strongly interconnected with the Parent. However, we can still swap children during the lifetime of the Parent thanks to pointers. This solution also allows to only forward-declare class Child in the header file unlike the alternative below.

    // --- header file
    class Child;
    
    class Parent
    {
    public:
        Parent();
        ~Parent();
        void renewChild();
    private:
        Child* child;
    };
    
    // --- source file
    #include "child.h"
    
    Parent::Parent()
        : child(new Child)
    {
    }
    
    Parent::~Parent()
    {
        delete child;
    }
    
    void Parent::renewChild()
    {
        delete child;
        child = new Child;
    }
    

    Disclaimer

    This example is a subject to the Rule of three/five/zero. I am intentionally letting the implementation of missing recommended methods up to the user, keeping this answer dialect-agnostic and as simple as possible.

    Composition without pointers

    Instead of writing constructor and destructor manually, you can just declare child in the class declaration and let the compiler to do the construction and destruction for you. This is valid as long as the class Child’s constructor requires no parameters (otherwise you’d need to write class Parent’s constructor manually) and the class Child is fully declared before the declaration of class Parent.

    #include "child.h"
    
    class Parent
    {
    private:
        Child child;
    };
    

    Aggregation without pointers

    To be complete, the alternative to using pointers for aggregation is using a reference. However, this prevents swapping children during the lifetime of the Parent object.

    class Child;
    
    class Parent
    {
    public:
        Parent(Child& ch) : child(ch) {}
    private:
        Child& child;
    };