Menu
Coddy logo textTech

Composition vs Inheritance

Part of the Object Oriented Programming section of Coddy's C++ journey — lesson 76 of 104.

When designing relationships between classes, you have two main options: inheritance ("is-a") and composition ("has-a"). Choosing the right approach significantly impacts your code's flexibility and maintainability.

Inheritance creates a tight coupling between classes. A Car that inherits from Vehicle is permanently bound to that relationship:

class Vehicle {
public:
    virtual void start() { std::cout << "Starting vehicle\n"; }
};

class Car : public Vehicle {
public:
    void start() override { std::cout << "Starting car\n"; }
};

Composition embeds objects as members, creating a more flexible relationship. A Car has an Engine rather than being an engine:

class Engine {
public:
    void start() { std::cout << "Engine running\n"; }
};

class Car {
private:
    Engine engine;  // Car HAS an Engine
public:
    void start() { engine.start(); }
};

The key advantage of composition is flexibility. You can easily swap components, change behavior at runtime, or combine multiple capabilities without the constraints of a rigid class hierarchy. Modern C++ design favors composition over inheritance in most cases.

Use inheritance when there's a genuine "is-a" relationship and you need polymorphism. Use composition when you want to reuse functionality or when the relationship is better described as "has-a" or "uses-a".

challenge icon

Challenge

Easy

Let's build a computer system simulator that demonstrates when to use composition versus inheritance. You'll model a computer that has components (composition) rather than being a component, showing how composition provides flexibility to swap parts at runtime.

You'll organize your code across three files:

  • Components.h: Define the individual components that a computer can contain.

    Create a CPU class with a private std::string model and int cores. Provide a constructor that takes both values, a process() method that prints CPU [model] processing with [cores] cores, and a getModel() method.

    Create a Memory class with a private int sizeGB. Provide a constructor, a load() method that prints Loading data into [sizeGB]GB RAM, and a getSize() method.

    Create a Storage class with a private std::string type (like "SSD" or "HDD") and int capacityGB. Provide a constructor, a read() method that prints Reading from [capacityGB]GB [type], and getter methods for both fields.

  • Computer.h: Define a Computer class that uses composition to combine components.

    Your Computer should have private member objects: a CPU, a Memory, and a Storage. Use the constructor initialization list to initialize these components from parameters.

    Implement these methods:

    • boot() — prints Booting computer..., then calls load() on memory, read() on storage, and process() on CPU, each on separate lines
    • specs() — prints the computer's specifications in this format:
      Computer Specs:
      - CPU: [model] ([cores] cores)
      - RAM: [size]GB
      - Storage: [capacity]GB [type]
  • main.cpp: Read six inputs (each on a separate line):
    1. CPU model name (string)
    2. Number of CPU cores (integer)
    3. Memory size in GB (integer)
    4. Storage type (string, e.g., "SSD")
    5. Storage capacity in GB (integer)
    6. A command: either "boot" or "specs"

    Create the component objects, then create a Computer using composition. Based on the command, either call boot() or specs().

For example, with inputs Intel i7, 8, 16, SSD, 512, and boot:

Booting computer...
Loading data into 16GB RAM
Reading from 512GB SSD
CPU Intel i7 processing with 8 cores

With inputs AMD Ryzen, 6, 32, HDD, 1000, and specs:

Computer Specs:
- CPU: AMD Ryzen (6 cores)
- RAM: 32GB
- Storage: 1000GB HDD

Notice how the Computer class doesn't inherit from any component—it contains them. This "has-a" relationship means you could easily create computers with different component combinations, or even swap components later if you added setter methods. This flexibility is the key advantage of composition over inheritance.

Cheat sheet

When designing relationships between classes, choose between inheritance ("is-a") and composition ("has-a").

Inheritance creates tight coupling between classes:

class Vehicle {
public:
    virtual void start() { std::cout << "Starting vehicle\n"; }
};

class Car : public Vehicle {
public:
    void start() override { std::cout << "Starting car\n"; }
};

Composition embeds objects as members, creating flexible relationships:

class Engine {
public:
    void start() { std::cout << "Engine running\n"; }
};

class Car {
private:
    Engine engine;  // Car HAS an Engine
public:
    void start() { engine.start(); }
};

When to use each:

  • Use inheritance for genuine "is-a" relationships when you need polymorphism
  • Use composition to reuse functionality or for "has-a"/"uses-a" relationships
  • Modern C++ design favors composition for its flexibility—you can swap components, change behavior at runtime, and combine capabilities without rigid hierarchies

Try it yourself

#include <iostream>
#include <string>
#include "Computer.h"

using namespace std;

int main() {
    // Read inputs
    string cpuModel;
    getline(cin, cpuModel);  // CPU model name (may contain spaces)
    
    int cpuCores;
    cin >> cpuCores;  // Number of CPU cores
    
    int memorySize;
    cin >> memorySize;  // Memory size in GB
    
    string storageType;
    cin >> storageType;  // Storage type (e.g., "SSD")
    
    int storageCapacity;
    cin >> storageCapacity;  // Storage capacity in GB
    
    string command;
    cin >> command;  // Command: "boot" or "specs"

    // TODO: Create component objects (CPU, Memory, Storage)
    
    // TODO: Create a Computer object using composition
    
    // TODO: Based on the command, call either boot() or specs()

    return 0;
}
quiz iconTest yourself

This lesson includes a short quiz. Start the lesson to answer it and track your progress.

All lessons in Object Oriented Programming