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
EasyLet'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
CPUclass with a privatestd::string modelandint cores. Provide a constructor that takes both values, aprocess()method that printsCPU [model] processing with [cores] cores, and agetModel()method.Create a
Memoryclass with a privateint sizeGB. Provide a constructor, aload()method that printsLoading data into [sizeGB]GB RAM, and agetSize()method.Create a
Storageclass with a privatestd::string type(like "SSD" or "HDD") andint capacityGB. Provide a constructor, aread()method that printsReading from [capacityGB]GB [type], and getter methods for both fields.Computer.h: Define aComputerclass that uses composition to combine components.Your
Computershould have private member objects: aCPU, aMemory, and aStorage. Use the constructor initialization list to initialize these components from parameters.Implement these methods:
boot()— printsBooting computer..., then callsload()on memory,read()on storage, andprocess()on CPU, each on separate linesspecs()— 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):- CPU model name (string)
- Number of CPU cores (integer)
- Memory size in GB (integer)
- Storage type (string, e.g., "SSD")
- Storage capacity in GB (integer)
- A command: either "boot" or "specs"
Create the component objects, then create a
Computerusing composition. Based on the command, either callboot()orspecs().
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 coresWith inputs AMD Ryzen, 6, 32, HDD, 1000, and specs:
Computer Specs:
- CPU: AMD Ryzen (6 cores)
- RAM: 32GB
- Storage: 1000GB HDDNotice 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;
}
This lesson includes a short quiz. Start the lesson to answer it and track your progress.
All lessons in Object Oriented Programming
1Fundamentals of OOP
External FilesC++ Build & CompilationHeader Files & Source FilesNamespaces & ScopeIntroduction to OOP in C++Classes vs ObjectsThe 'this' PointerMethods (Member Functions)Attributes (Data Members)Ctors & Dtors BasicsRecap - Simple Calculator4Class Properties
Instance vs Static MembersGetters and SettersConst Member FunctionsMutable KeywordStatic Methods and VariablesFriend Functions & ClassesRecap - Bank Account Manager7Inheritance
Basic InheritanceInheritance Access LevelsCtor & Dtor Call OrderMethod OverridingVirtual Functions & VTableMultiple InheritanceVirtual InheritanceRecap - Employee Hierarchy2Memory Management
Stack vs Heap MemoryPointers and ReferencesDynamic Memory (new/delete)Smart Pointers in C++RAII in C++Recap - Dynamic Array Manager5Encapsulation
Access Specifiers in C++Access Specifiers In DepthInformation HidingStruct vs ClassNested & Inner ClassesRecap - Student Records System8Polymorphism
Compile vs Runtime PolymorphFunction OverloadingVirtual Functions RevisitedPure Virtual FunctionsAbstract ClassesInterface Design in C++Dynamic Casting & RTTIRecap - Shape Calculator11Advanced OOP Concepts
Composition vs InheritanceMixins via CRTPPimpl IdiomType ErasureEnum Classes & Strong TypingException Handling in OOPCustom Exception Hierarchies3Constructors & Destructors
Default ConstructorParameterized ConstructorCopy ConstructorMove ConstructorConstructor Init ListsDelegating ConstructorsDestructor Deep DiveRule of Three / Five / ZeroRecap - String Class6Operator Overloading
Intro to Operator OverloadArithmetic Operator OverloadComparison Operator OverloadStream OperatorsAssignment Operator Overload[] and () Operator OverloadType Conversion OperatorsRecap - Matrix Class9Templates
Function TemplatesClass TemplatesTemplate SpecializationVariadic TemplatesSFINAE & Type Traits BasicsRecap - Generic Container