Skip to content

Bank 7 — Composition and association

Eleven exercises on who owns what — the difference between an object that dies with its owner and one that merely refers to something that outlives it. Module 9. Levels L1–L6.

Solutions are in Solutions and answers; nudges are in Hints. Attempt first.


Exercises

E71 — Three relationships L1 concept

Task. In one sentence each, define composition, aggregation and plain association, and give the lifetime rule that distinguishes them. Then classify: a car's engine; a car's driver for today's trip; a car's manufacturer.


E72 — Members die first L2 predict

#include <iostream>

class Part {
public:
    explicit Part(char id) : id_(id) { std::cout << "make " << id_ << '\n'; }
    ~Part() { std::cout << "drop " << id_ << '\n'; }
private:
    char id_;
};

class Machine {
public:
    Machine() : first_('A'), second_('B') {
        std::cout << "machine ready\n";
    }
    ~Machine() { std::cout << "machine gone\n"; }
private:
    Part first_;
    Part second_;
};

int main() {
    Machine m;
}

Task. Write the output in order. State the rule for the order in which the members are constructed, and whether they are destroyed in the same order.


E73 — Has-a by value L2 complete

#include <iostream>
#include <string>

class Address {
public:
    Address(std::string street, std::string city)
        : street_(street), city_(city) {}
    std::string city() const { return city_; }
private:
    std::string street_;
    std::string city_;
};

class Person {
public:
    // TODO: a constructor taking (std::string name, Address address) that stores
    //       both, and a city() accessor that forwards to the address
    std::string name() const { return name_; }
    std::string city() const { /* TODO */ }
private:
    std::string name_;
    Address     address_;      // composition: a person owns their address
};

int main() {
    Person p{ "Zara", Address{ "Mall Road", "Lahore" } };
    std::cout << p.name() << " lives in " << p.city() << '\n';
}

Task. Complete the constructor and city(). Why does address_ need no special handling in a destructor?


E74 — The library owns its items L2 trace scenario

#include <iostream>
#include <string>
#include <vector>

class Item {
public:
    explicit Item(std::string title) : title_(title) {
        std::cout << "acquire " << title_ << '\n';
    }
    ~Item() { std::cout << "discard " << title_ << '\n'; }
    std::string title() const { return title_; }
private:
    std::string title_;
};

class Library {
public:
    void add(std::string title) {
        items_.push_back(Item{title});
    }
    std::size_t size() const { return items_.size(); }
private:
    std::vector<Item> items_;
};

int main() {
    Library lib;
    lib.add("Dune");
    lib.add("Emma");
    std::cout << "holds " << lib.size() << '\n';
}

Task. Write every line of output in order — including the ones produced by the temporary Item created inside add, and by the reallocation when the vector grows. (This is deliberately fiddly; trace it carefully.)


E75 — Value to reference L3 modify scenario

A reservation holds its room by value, so copying a reservation duplicates the room and the two copies can drift apart.

#include <iostream>
#include <string>

class Room {
public:
    explicit Room(std::string number) : number_(number), occupied_(false) {}
    std::string number() const { return number_; }
    bool occupied() const { return occupied_; }
    void occupy() { occupied_ = true; }
private:
    std::string number_;
    bool        occupied_;
};

class Reservation {
public:
    explicit Reservation(Room room) : room_(room) {}
    void book() { room_.occupy(); }
    Room room_;               // public so you can inspect it
};

int main() {
    Room r{"101"};
    Reservation a{r};
    Reservation b{r};
    a.book();
    std::cout << b.room_.occupied() << '\n';   // prints 0 — the drift
}

Task. Change the relationship so that a room is shared, not copied, and the drift disappears. State the lifetime rule you are now relying on: who must outlive whom, and what would go wrong if a room were destroyed while a reservation still referred to it.


E76 — The member who left L3 debug scenario

A reading room stores a pointer to each member who is present.

#include <iostream>
#include <string>
#include <vector>

class Member {
public:
    explicit Member(std::string name) : name_(name) {}
    std::string name() const { return name_; }
private:
    std::string name_;
};

class ReadingRoom {
public:
    void checkIn(Member& m) { present_.push_back(&m); }
    void list() const {
        for (const Member* m : present_) std::cout << m->name() << '\n';
    }
private:
    std::vector<Member*> present_;
};

void session(ReadingRoom& room) {
    Member temp{"Imran"};        // local to this function
    room.checkIn(temp);
}

int main() {
    ReadingRoom room;
    session(room);
    room.list();                 // reads a member that no longer exists
}

Task. Explain what is wrong and when it becomes a problem (not "immediately" — be precise about the object's lifetime). Then repair the design: say what the reading room should store, and who must own the members.


E77 — Build and destroy L3 predict

#include <iostream>

class A { public: A()  { std::cout << "A+ "; } ~A() { std::cout << "A- "; } };
class B { public: B()  { std::cout << "B+ "; } ~B() { std::cout << "B- "; } };

class Owner {
public:
    Owner() : a_(), b_() { std::cout << "O+ "; }
    ~Owner() { std::cout << "O- "; }
private:
    A a_;
    B b_;
};

int main() {
    Owner o;
}

Task. Write the output in order. Then state the rule for when members are constructed relative to the Owner constructor body, and why members are destroyed in the reverse of that order.


E78 — Pick the relationship L4 analyze

For each pair, decide whether it is composition, aggregation or plain association, and defend it in one sentence each:

  1. A Playlist and the Songs in it; deleting the playlist deletes the songs.
  2. A Playlist and the Songs in it; deleting the playlist leaves the songs in the music library.
  3. A Doctor and their Appointments.
  4. A Course and its Lecturer.
  5. A Document and its SpellChecker.

Task. For each, also say whether the relationship should be modelled by a value member, a pointer/reference member, or nothing at all.


E79 — Owning each other L4 debug

A programmer models a marriage with two classes that each hold the other by value.

class Husband;
class Wife {
    Husband h_;      // a Wife contains a Husband ...
};
class Husband {
    Wife w_;         // ... and a Husband contains a Wife
};

Task. Explain why this cannot compile, in terms of object size, and name the two ways a real modelling would resolve it (choose one and say when each is right). How many bytes would a Wife need if the compiler did allow it?


E80 — Design course registration L5 design scenario

A university lets students register for courses. A course has a capacity; a student may register for several courses; a registration must know which student and which course it refers to. Courses and students both outlive any individual registration. The registry must be able to answer "who is in this course?" and "how many courses is this student in?".

Task. Design the types and their relationships. Give:

  1. each type's name and one-line responsibility;
  2. for each relationship: composition, aggregation or association, and whether the member is by value, a pointer, or a reference;
  3. the invariant that capacity must never be exceeded, and the one operation that enforces it;
  4. what owns a Registration, and what happens to the registrations when a course is cancelled.

How you will be judged. On whether you said who owns what, for every relationship.


E81 — Untangle the ownership L6 refactor scenario

A booking system has grown so that Booking holds a Room by value, Room holds a Booking by pointer, and Hotel holds copies of both. Deletions cause crashes, and nobody can tell who is supposed to own a room.

Task. Write an ownership report and refactor plan. It must:

  1. draw the current ownership as a list of "X owns Y" claims, noting which are contradictory or circular;
  2. state the lifetime of each object (who creates it, who destroys it);
  3. propose the corrected relationships, with the member form for each;
  4. name the single change that removes the crash, and how you would prove it is gone (you cannot use a debugger yet — say what you would add to the code).

Success. Every resource has exactly one owner, and the ownership can be read off the declarations.


Previous topic: Polymorphism and interfaces · Next topic: Copying and resource management · Hints: hints.md · Solutions: solutions.md