Skip to content

Mini Project 7 — Playlist Ownership

Difficulty: Intermediate · Estimated effort: 4–5 hours · Prerequisites: Module 9 — Composition and Association, Module 11 — RAII and Smart Pointers · Combines: owning vs observing raw pointers, unique_ptr, composition, an ownership diagram

No complete solution is provided. The design question — who owns a track — is the whole project, and answering it for you would remove the work.


1. Scenario

A community radio station keeps a library of tracks and builds programmes from them. The same track can appear in several programmes at once, a programme can be deleted without losing the track, and the library is cleared only when the station closes for the night.

The station's previous program kept every track in one global vector, which meant deleting a programme sometimes deleted a track another programme was still using.


2. Problem statement

Build a track library and a set of playlists over it. Decide, and express in types, who owns a track and who merely refers to it — then demonstrate that deleting a playlist cannot damage the library, and that closing the library cannot damage a playlist that still refers to it.


3. User requirements

  • [ ] Add tracks to the library; the library keeps them.
  • [ ] Create playlists that refer to tracks from the library.
  • [ ] The same track may appear in more than one playlist.
  • [ ] Delete a playlist and confirm the library is unchanged.
  • [ ] Print a playlist, with each track's details.

4. Functional requirements

  • [ ] Track with title, artist and duration.
  • [ ] Library owning its tracks via std::unique_ptr.
  • [ ] Playlist holding non-owning references to tracks.
  • [ ] A way to look a track up in the library by identity, not by copying it.
  • [ ] Removing a track from the library is refused, or leaves every playlist consistent — you choose, and you document the choice.
  • [ ] Library is not copyable; it is movable, or not, by a documented rule.

5. Non-functional requirements

  • [ ] Only one owner per track. A track destroyed twice is the bug this project exists to prevent; show that it cannot happen.
  • [ ] No new or delete in your own code.
  • [ ] Builds warning-free with g++ -std=c++17 -Wall -Wextra -Werror.
  • [ ] Nothing outside the library frees or outlives the library's tracks.
  • [ ] Every raw pointer in the code is a clearly-labelled observer.

6. Suggested features

  • Playlist ordering: append, insert, move a track up or down.
  • Total duration of a playlist, in minutes and seconds.
  • A playlist of playlists ("the evening programme"), and a comment on what it owns.
  • A Track shared by the library and a separate cache, if you want to practise shared_ptr — with a written answer to which of the two owners is authoritative.

7. OOP concepts involved

Concept Where it appears
Composition The library owns its tracks; a track's lifetime ends with the library's
Aggregation A playlist refers to tracks that outlive it and that it does not own
Ownership in types unique_ptr for owning, Track* for observing, and no ambiguity
Smart pointers The default in this project is unique_ptr, not shared_ptr; justify any use
Copy control Copying a library would duplicate every track; deciding is part of the design
Diagram literacy An ownership diagram you draw, marking multiplicities and diamonds

8. Suggested design considerations

Draw the ownership diagram before writing code. Two boxes and three arrows will do. If you cannot say who dies first, the design is not ready — and the compiler will let you build it anyway.

Reach for shared_ptr last. Shared ownership means "nobody can say who should keep this alive". Here the answer is knowable: the library owns tracks, playlists borrow them. Use the type that encodes that answer.

Decide what makes a track unique. A title is not an identity — two different recordings can share one. Give tracks an identifier and say what it means.

Handle the removal case explicitly. If the library drops a track that a playlist still refers to, something must happen. Choose: refuse, remove from every playlist, or a lazy validity check. Each is defensible; an unhandled dangling reference is not.


9. Minimum viable version

Track, Library owning vector<unique_ptr<Track>>, Playlist holding observers, lookup by identifier, and a main that prints a playlist, deletes it, then prints the library to show it is intact.


10. Intermediate extension

Add playlist reordering and total duration, then break your design on purpose: destroy the library first while a playlist still exists, and either show that it is impossible (by making the playlist hold a reference to the library) or document precisely what a user must not do.


11. Advanced extension

Add a second consumer of tracks — a "recently played" history — and convert one part of the design to shared_ptr. Then answer in writing: what did shared ownership buy, what did it cost, and could the same problem have been solved by strengthening the ownership rule instead?


12. Testing checklist

  • [ ] A track added once appears once and has a unique identity
  • [ ] Adding the same track to two playlists does not copy it — verified with a copy counter
  • [ ] Deleting a playlist leaves every library track alive and unchanged
  • [ ] Deleting a playlist does not affect another playlist sharing a track
  • [ ] A track identifier that does not exist is refused rather than guessed at
  • [ ] Copying the library is a compile error (or behaves as documented)
  • [ ] At program end, each Track is destroyed exactly once — verified with a counter

13. Documentation checklist

  • [ ] The ownership diagram, drawn and checked against the declarations
  • [ ] One sentence per class saying what it owns
  • [ ] The rule for removing a track, with the reason
  • [ ] Why unique_ptr here rather than shared_ptr
  • [ ] What a copy of a library or playlist would mean, and why it is allowed or forbidden

14. Self-evaluation rubric

Criterion Excellent looks like Weight
Ownership design Single, unambiguous owner; observers clearly marked; diagram matches code 35%
Lifetime safety Destroyed exactly once; deletion orders cannot break the design 25%
Interface Lookups return observers, not copies; identifiers meaningful 15%
Failure handling Removal and missing-identifier cases decided and tested 15%
Documentation Diagram plus the reasoning, including what you rejected 10%

Related: Mini projects · Project roadmap · All projects · Module 11 — RAII and Smart Pointers