Understanding Inheritance and Composition in C++

In object-oriented programming, two fundamental mechanisms for building relationships between classes are composition and inheritance. While both enable code reuse, they serve distinct purposes and model different kinds of relationships.

Composition: "Has-a" Relationship

Composition represents a whole-part relationship, where one class contains objects of other classes as members. The lifetime of the contained objects is tied to the containing object — they are created and destroyed together.

For example, a computer is composed of components such as memory, disk, CPU, and motherboard. Each componnet is an independent object, but the computer owns and manages them.

#include <iostream>
#include <string>

using namespace std;

class RAM {
public:
    RAM() { cout << "RAM constructed\n"; }
    ~RAM() { cout << "RAM destroyed\n"; }
};

class Storage {
public:
    Storage() { cout << "Storage constructed\n"; }
    ~Storage() { cout << "Storage destroyed\n"; }
};

class Processor {
public:
    Processor() { cout << "Processor constructed\n"; }
    ~Processor() { cout << "Processor destroyed\n"; }
};

class Motherboard {
public:
    Motherboard() { cout << "Motherboard constructed\n"; }
    ~Motherboard() { cout << "Motherboard destroyed\n"; }
};

class Desktop {
    RAM ram;
    Storage disk;
    Processor cpu;
    Motherboard board;
public:
    Desktop() { cout << "Desktop constructed\n"; }
    void boot() { cout << "Booting system...\n"; }
    void shutdown() { cout << "Shutting down...\n"; }
    ~Desktop() { cout << "Desktop destroyed\n"; }
};

int main() {
    Desktop pc;
    pc.boot();
    return 0;
}

When this program runs, the member objects (ram, disk, etc.) are constructed in dcelaration order before the Desktop constructor runs, and destroyed in reverse order after the destructor completes. This demonstrates tight lifecycle coupling — a hallmark of composition.

Inheritance: "Is-a" Relationship

Inheritance models a specialization relationship: a subclass is a more specific version of its parent. The derived class inherits all public and protecetd members of the base class and can extend or override them.

Key characteristics of inheritance:

  • A derived class inherits all accessible members of the base class.
  • A derived class object can be treated as an instance of the base class (polymorphic substitution).
  • New methods or data members can be added to the derived class.
#include <iostream>

using namespace std;

class Vehicle {
protected:
    int speed;
public:
    Vehicle() : speed(0) { cout << "Vehicle created\n"; }
    void accelerate() { cout << "Accelerating...\n"; }
    void brake() { cout << "Braking...\n"; }
    virtual void info() const { cout << "This is a vehicle.\n"; }
};

class Car : public Vehicle {
public:
    Car() { cout << "Car created\n"; }
    void openTrunk() { cout << "Trunk opened\n"; }
    void info() const override { cout << "This is a car.\n"; }
};

int main() {
    Car myCar;
    myCar.accelerate();   // Inherited from Vehicle
    myCar.openTrunk();    // Defined in Car
    myCar.info();         // Overridden method

    Vehicle* v = &myCar;  // Car can be treated as Vehicle
    v->info();            // Calls Car::info() due to virtual dispatch

    return 0;
}

In this example, Car inherits accelerate() and brake() from Vehicle, adds its own method openTrunk(), and overrides info() to provide specialized behavior. The pointer assignment Vehicle* v = &myCar; is valid because a Car is a Vehicle.

Practical Use Case: Extending Functionalities via Inheritance

Consider a system where different computer types share common hardware behavior but differ in software features. Composition handles the hardware, while inheritance handles the software specialization.

#include <iostream>
#include <string>

using namespace std;

class RAM {
public:
    RAM() { cout << "RAM initialized\n"; }
    ~RAM() { cout << "RAM released\n"; }
};

class SSD {
public:
    SSD() { cout << "SSD initialized\n"; }
    ~SSD() { cout << "SSD released\n"; }
};

class CPU {
public:
    CPU() { cout << "CPU initialized\n"; }
    ~CPU() { cout << "CPU released\n"; }
};

class BaseSystem {
    RAM ram;
    SSD storage;
    CPU processor;
public:
    BaseSystem() { cout << "Base system powered on\n"; }
    void powerOn() { cout << "System booting...\n"; }
    void powerOff() { cout << "System shutting down...\n"; }
    ~BaseSystem() { cout << "Base system powered off\n"; }
};

class WindowsPC : public BaseSystem {
    string os;
public:
    WindowsPC() : os("Windows 11") {}
    void installOS(const string& newOS) { os = newOS; }
    void displayOS() const { cout << "OS: " << os << "\n"; }
};

class Macintosh : public BaseSystem {
public:
    void displayOS() const { cout << "OS: macOS\n"; }
};

int main() {
    WindowsPC pc;
    pc.powerOn();
    pc.installOS("Windows 10");
    pc.displayOS();

    cout << "\n";

    Macintosh mac;
    mac.powerOn();
    mac.displayOS();

    return 0;
}

Here, both WindowsPC and Macintosh inherit the core hardware behavior from BaseSystem (via composition of components), while each adds its own OS-specific behavior through inheritance. This avoids code duplication and promotes maintainability.

Summary

  • Composition models "has-a" relationships and is used when one object contains others as parts.
  • Inheritance models "is-a" relationships and is used to extend or specialize existing behavior.
  • Derived classes inherit all accessible members from their base classes and may add or override functionality.
  • Objects of a derived class can be used wherever the base class is expected, enabling polymorphism.
  • Use inheritance for behavioral specialization; use composition for structural composition.

Tags: C++ Inheritance composition object-oriented-programming Polymorphism

Posted on Mon, 22 Jun 2026 17:29:42 +0000 by amirbwb