CT 301 — Ownership & Core Concepts
Ownership, Polymorphism,
and Smart Pointers
Memory is where C++ programs go wrong
- Most serious bugs in large C++ codebases are memory bugs: use-after-free, leaks, double-free.
- The fix is not "be more careful." It is making ownership explicit in the type system.
- This homework teaches the modern answer: one owner per object, enforced by the compiler.
We use raw new/delete only in HW2, on purpose, so you feel why HW1's approach is safer.
A class bundles data with behavior
- An object holds data (members) and the functions that act on it.
- Members are private; the outside world uses public methods.
- A constructor sets up the object; a destructor cleans it up.
class Date { public: Date(int y,int m,int d); // constructor int year() const; // behavior private: int y_, m_, d_; // data };
One interface, many behaviors
- A base class declares
virtualfunctions, a contract. - Derived classes override them. The same call does the right thing per type.
- HW1:
Itemis the base;Equipment,Consumable,Borrowedbehave differently.
bool Consumable::canLoan() const { return false; } // a cable: no bool Equipment::canLoan() const { return true; } // a laptop: yes // item->canLoan() picks the right one at run time
There is also a pure virtual (a function the base leaves unimplemented, = 0), which we covered in class. It forces every derived type to provide its own version. Same idea, stronger contract.
Virtual destructor & no slicing
Virtual destructor
If you delete a derived object through a base pointer, the base needs a virtual ~Item(), or cleanup is wrong.
Don't slice
Store items as unique_ptr<Item>, never by value. Copying an Item by value chops off the derived part.
Both are provided/enforced in HW1, but you should know why they exist.
Ownership: who is responsible for deleting it?
- Every object's memory must be freed exactly once, by one owner.
- The owner decides when the object dies. Everyone else is just looking.
- In C++ the modern owner is
std::unique_ptr<T>: one owner, frees automatically.
Deed vs. address. The owner holds the deed to the house. Everyone else just has the street address, they can visit and look, but they can't tear it down.
One owner, freed automatically (RAII)
unique_ptrowns the object and deletes it for you when it goes out of scope.- You write no
delete. This is RAII: the object's lifetime follows the owner's scope. - A
unique_ptrcannot be copied, that would mean two owners. It can only be moved.
auto kbd = std::make_unique<Equipment>(...); // kbd owns it // when kbd dies, the Equipment is deleted. No delete needed.
Transferring ownership = moving
- To give ownership to someone else, you move the
unique_ptr. - After the move, the old variable is empty; the new one owns the object.
- The compiler forces this: you literally cannot copy it. That is the safety.
std::unique_ptr<Item> owned = extract(inStock_, id); // take it out owned->setState(State::OnLoan); onLoan_.push_back(std::move(owned)); // hand ownership over // 'owned' is now empty; onLoan_ owns the item
A raw pointer here means "I'm just looking"
- Our rule: a raw
Item*is non-owning, it lets you read the item but never deletes it. - HW1 selections return
std::vector<Item*>, borrowed pointers into items the inventory still owns. - Ownership is always a smart pointer (
unique_ptr); raw pointers only borrow.
OK: raw pointer that borrows. Banned in HW1: raw owning pointer, new, delete, malloc, calloc, realloc, free.
Two operations, one careful distinction
gather = borrow
Copies non-owning Item* pointers so you can look at many items. Ownership stays put.
extract = take
Removes the item from its list and hands you the unique_ptr. Ownership moves to you.
Danger: after extract, that unique_ptr is the only thing keeping the item alive. Forget to move it into a list and the item is destroyed, not leaked.
Two different "kinds" — keep them apart
Type (what it is)
Equipment / Consumable / Borrowed. Fixed for life. Polymorphism.
State (where it is)
InStock → OnLoan → back; or → Sold; or → Surplus. Changes over time. A small state machine.
The inventory keeps four lists, one per state. Changing state is moving the item's unique_ptr to the matching list.
Why not shared_ptr everywhere?
shared_ptrallows many owners and frees the object when the last one leaves.- Here that would be wrong by design: an item has exactly one home (the inventory) and one state.
- It also costs more (reference counting) and hides the clear single-owner story we want to teach.
Use the simplest ownership that fits. For HW1 that is unique_ptr.
Five things to remember
- One owner per object. In HW1 the inventory owns every item.
- unique_ptr frees automatically (RAII), you write no
delete. - Transfer ownership by moving, never copying.
- Raw pointers borrow, they look, they never delete.
- Type is fixed; state changes. A state change is a move between lists.
Now let's look at the homework
Part 2 puts these ideas to work: the inventory, the items, and what you'll build.