Object Relationships in C++
Table of Contents
- Types of Relationships
- Association
- Aggregation
- Composition
- Dependency
- Inheritance vs Composition
- Best Practices
Types of Relationships
| Relationship | Strength | Lifetime | Ownership |
|---|---|---|---|
| Dependency | Weakest | Temporary | None |
| Association | Weak | Independent | None |
| Aggregation | Medium | Independent | Shared |
| Composition | Strong | Dependent | Exclusive |
| Inheritance | Strongest | Same | IS-A |
Association
Objects know about each other but live independently.
class Teacher;
class Student {
vector<Teacher*> teachers; // Knows teachers
public:
void addTeacher(Teacher* t) { teachers.push_back(t); }
};
class Teacher {
vector<Student*> students; // Knows students
public:
void addStudent(Student* s) { students.push_back(s); }
};
// Both exist independently
Teacher t;
Student s;
t.addStudent(&s);
s.addTeacher(&t);
Key: Neither owns the other. Lifetimes are independent.
Aggregation
"HAS-A" relationship with shared ownership.
class Engine { };
class Car {
Engine* engine; // Pointer - doesn't own
public:
Car(Engine* e) : engine(e) {}
// No delete in destructor - engine owned elsewhere
};
Engine sharedEngine;
Car car1(&sharedEngine);
Car car2(&sharedEngine); // Same engine!
Key: Part can exist without whole. Shared lifetime.
Composition
"HAS-A" with exclusive ownership. Part dies with whole.
class Engine { };
class Car {
Engine engine; // Object - owns it
public:
// Engine created with Car, destroyed with Car
};
// OR with pointer
class Car2 {
unique_ptr<Engine> engine;
public:
Car2() : engine(make_unique<Engine>()) {}
// Engine automatically destroyed
};
Key: Part cannot exist without whole. Exclusive ownership.
Dependency
Weakest relationship. Temporary usage.
class Printer {
public:
void print(const Document& doc) { // Uses Document
cout << doc.getContent();
}
// No Document member - just parameter
};
Key: Uses another class briefly (parameter, local variable).
Inheritance vs Composition
Prefer Composition
// Instead of:
class Stack : public vector<int> { }; // BAD
// Use:
class Stack {
vector<int> data; // GOOD - composition
public:
void push(int x) { data.push_back(x); }
void pop() { data.pop_back(); }
int top() const { return data.back(); }
};
Use Inheritance For IS-A
class Animal { };
class Dog : public Animal { }; // Dog IS-A Animal - OK
Composition Benefits
- Less coupling
- Easier to change
- Hide implementation
- Combine behaviors
Best Practices
✅ Do
// 1. Composition with unique_ptr for exclusive ownership
class Car {
unique_ptr<Engine> engine;
};
// 2. Aggregation with raw pointer or shared_ptr
class Team {
vector<Player*> players; // Doesn't own
};
// 3. Clear ownership semantics
class Document {
unique_ptr<Content> content; // Owns
Author* author; // References
};
❌ Don't
// 1. Unclear ownership
class Bad {
Object* obj; // Owner? Reference? Unknown!
};
// 2. Composition with raw new
class Leaky {
Engine* engine = new Engine(); // Who deletes?
};
Quick Reference
// Composition - owns exclusively
class Whole {
Part part; // Direct member
unique_ptr<Part> ptr; // Or smart pointer
};
// Aggregation - shared reference
class Container {
vector<Item*> items; // Raw pointers
vector<shared_ptr<Item>> shared; // Or shared_ptr
};
// Association - bidirectional knowledge
class A { B* knows; };
class B { A* knows; };
// Dependency - temporary use
void func(const Used& u); // Parameter
Compile & Run
g++ -std=c++17 -Wall examples.cpp -o examples && ./examples