Significantly, smart pointers don't alter, limit, or enhance the semantics of the class relationships implemented with pointers. Aggregation is a relationship between a whole and part class, and C++ programs implement it with pointers. Used to implement aggregation, smart pointer's primary advantage is making the implementation more robust by identifying some errors at compile time, simplifying or eliminating pointer-management functions, and preventing memory leaks. Contrasting the raw pointer implementation (introduced in Chapter 10) with smart pointers is informative.
class Whole
{
    private:
        Part* part = nullptr;
};
  | 
Whole() {}
Whole(Part* p) : part(p) {}
 
 
 
  | 
~Whole()
{
    if (part != nullptr)
        delete part;
}
 
 | 
void set_part(Part* p)
{
    if (part != nullptr)
        delete part;
    part = p;
} | 
| (a) | (b) | (c) | (d) | 
Although not a panacea, smart pointers simplify setter functions and often eliminate the need for destructors. In the context of aggregation, they provide these benefits by wrapping raw the pointers with objects that track or count how many whole objects use (i.e., refer or point to) a part and automatically deallocate it when it is no longer used (i.e., when the count goes to zero).
![]()  | 
int main()
{
    Whole whole("Widget");
    whole.display();
    whole.set_part(new Part("Bolt"));
    whole.display();
    return 0;
} | 
| (a) | (b) | 
class Part
{
    private:
        string name;
    public:
        Part(string n) : name(n) {}
        ~Part() { cout << "Part dtor: " << name << endl; }
        void display() { cout << name << endl; }
};
  | 
class Whole
{
    private:
        shared_ptr<Part> part;
    public:
        Whole(string n) { part = make_shared<Part>(n); }
        ~Whole() { cout << "Whole dtor\n"; }
        void set_part(Part* n) { part.reset(n); }
        void display() { cout << "Whole: "; part->display(); }
}; | 
| (c) | (d) | 
delete or when it goes out of scope - the shared_ptr automatically destroys the Part if its reference count (i.e., use_count) is 1.Whole: Widget Part dtor: Widget Whole: Bolt Whole dtor Part dtor: Bolt
Aggregation's weak binding strength allows the whole to share its part with other classes in a program. When two whole classes share a part, its ownership is a fundamental concern. Chapter 10 illustrated this problem by imagining a warehouse with many engines and sharing one with a race car. We begin the smart pointer version by limiting the warehouse to a single engine. Formally, a Car "has an" Engine and a Warehouse "has an" Engine, with the phrase "has a" indicating an aggregation relationship. Now, imagine that the program destroys one whole object (either a Car or Warehouse). Should the destroyed object's destructor also destroy the aggregated Engine object? Alternatively, imagine that the program replaces the Engine in one of the whole classes. What should the program do with the replaced Engine object?
| UML Class Diagram | Abstract Memory Representation | 
|---|---|
![]()  | 
![]()  | 
class Transmission
{
    private:
        string type;
    public:
        Transmission(string t) : type(t) {}
        ~Transmission() { cout << "Transmission dtor" << endl; }
        friend ostream& operator<<(ostream& out, Transmission& me)
        {
            out << "Transmission: " << me.type;
            return out;
        }
}; | 
class Engine
{
    private:
        int size;
    public:
        Engine(int s) : size(s) {}
        ~Engine() { cout << "Engine dtor" << endl; }
        friend ostream& operator<<(ostream& out, Engine& me)
        {
            out << "Engine: " << me.size;
            return out;
        }
}; | 
class Car
{
    private:
        unique_ptr<Transmission>  trans;
        shared_ptr<Engine>        engine;
    public:
        Car(string t) : trans(make_unique<Transmission>(t)) {}
        ~Car() { cout << "Car dtor" << endl; }
        void set_engine(shared_ptr<Engine> e) { engine = e; }
        friend ostream& operator<<(ostream& out, Car& me)
        {
            out << *me.engine << " " << *me.trans.get();
            return out;
        }
}; | 
class Warehouse
{
    private:
        shared_ptr<Engine>  engine;
    public:
        ~Warehouse() { cout << "Warehouse dtor" << endl; }
        void set_engine(shared_ptr<Engine> e) { engine = e; }
        friend ostream& operator<<(ostream& out, Warehouse& me)
        {
            out << *me.engine;
            return out;
        }
};
 
  | 
int main()
{
    Car			c("Automatic");
    Warehouse		w;
    shared_ptr<Engine>	e = make_shared<Engine>(440);
    c.set_engine(e);
    w.set_engine(e);
    cout << "(1) Engine: " << *e << endl;
    cout << "(2) Car: " << c << endl;
    cout << "(3) Warehouse: " << w << endl << endl;
    e = make_shared<Engine>(380);
    //e.reset(new Engine(380));	// alternative
    c.set_engine(e);
    w.set_engine(e);
    cout << "(4) Engine: " << *e << endl;
    cout << "(5) Car: " << c << endl;
    cout << "(6) Warehouse: " << w << endl << endl;
    cout << "Destructor messages follow:" << endl;
    return 0;
} | 
(1) Engine: Engine: 440 (2) Car: Engine: 440 Transmission: Automatic (3) Warehouse: Engine: 440 Engine dtor: 440 (4) Engine: Engine: 380 (5) Car: Engine: 380 Transmission: Automatic (6) Warehouse: Engine: 380 Destructor messages follow: Warehouse dtor Car dtor Engine dtor: 380 Transmission dtor  | 
| Program output | 
|---|
The program creates a new Engine object and installs it in the whole classes, leaving the previous Engine unused and ready for destruction. The second group of cout statements demonstrates the objects' new state. The program's termination triggers the destructors in the remaining objects.
The original Car-Warehouse problem imagined a well-funded race car with many replacement engines stored in a warehouse. The outlined solution used an array of Engine pointers, accommodating a maximum of ten spare engines. Replacing the array with a vector accrues several advantages to the solution and also demonstrates another STL class:
We convert the previous one-to-one aggregation to one-to-many by modifying the Warehouse class and the driver program, capitalizing and building on prior experience.
class Warehouse
{
    private:
        vector<shared_ptr<Engine>> engines;						// (a)
    public:
        ~Warehouse() { cout << "Warehouse dtor" << endl; }
        void add_engine(shared_ptr<Engine> e) { engines.push_back(e); }			// (b)
        shared_ptr<Engine> get_engine(int index) { return engines[index]; }		// (c)
        void display(int index) { engines[index].get()->display(); }			// (d)
        friend ostream& operator<<(ostream& out, Warehouse& me)
        {
            vector<shared_ptr<Engine>>::iterator i = me.engines.begin();		// (e)
            while (i != me.engines.end())
                out << "\t" << **i++ << endl;						// (f)
            return out;
        }
};
**i++ consists of three operators. The auto-increment operator has the highest precedence, and the indirection or dereference operators are right associative. Grouping parentheses illustrate the order of operation: *(*(i++)). The post-increment operator, ++, advances the iterator to the next vector element, but only after "using" it. The first dereference operation, *i, returns a shared pointer. The second dereferences the shared pointer, returning an Engine object. Together, the auto increment operator and while-loop iterate through the vector's elements.
#include "Car.h"
#include "Engine.h"
#include "Warehouse.h"
#include <iostream>
#include <memory>
using namespace std;
int main()
{
    Car          c("Automatic");
    Warehouse    w;
    w.add_engine(make_shared<Engine>(454));
    w.add_engine(make_shared<Engine>(440));
    w.add_engine(make_shared<Engine>(429));
    w.add_engine(make_shared<Engine>(427));
    w.add_engine(make_shared<Engine>(426));
    w.add_engine(make_shared<Engine>(380));
    w.add_engine(make_shared<Engine>(352));
    c.set_engine(w.get_engine(1));
    cout << "(1) Car: " << c << endl;
    cout << "(2) Warehouse:\n" << w << endl;
    c.set_engine(w.get_engine(5));
    cout << "(3) Car: " << c << endl;
    cout << "(4) Warehouse:\n" << w << endl;
    cout << "Destructor messages follow:" << endl;
    return 0;
} | 
(1) Car: Engine: 440 Transmission: Automatic (2) Warehouse: Engine: 454 Engine: 440 Engine: 429 Engine: 427 Engine: 426 Engine: 380 Engine: 352 (3) Car: Engine: 380 Transmission: Automatic (4) Warehouse: Engine: 454 Engine: 440 Engine: 429 Engine: 427 Engine: 426 Engine: 380 Engine: 352 Destructor messages follow: Warehouse dtor Engine dtor: 454 Engine dtor: 440 Engine dtor: 429 Engine dtor: 427 Engine dtor: 426 Engine dtor: 352 Car dtor Engine dtor: 380 Transmission dtor  | 
| View | Download | Comments | 
|---|---|---|
| aggregation.cpp | aggregation.cpp | A program implementing simple aggregation with smart pointers | 
| Car.h | Car.h | A program implementing shared aggregation with smart pointers | 
| Transmission.h | Transmission.h | |
| Engine.h | Engine.h | |
| Warehouse.h | Warehouse.h | |
| driver.cpp | driver.cpp | |
| Warehouse.h | Warehouse.h | One-to-many aggregation with shared pointers and vectors | 
| driver.cpp | driver.cpp |