Chapter 10 · Week 10

Constructors, Destructors, & Encapsulation

"Better is the end of a thing than its beginning." — Ecclesiastes 7:8
10

Watch lifecycle order in motion

Constructors fire in declaration order; destructors fire in reverse. Press play on the widget below and watch the stack discipline.

Object Lifecycle — Constructors & Destructors

Watch each object's constructor fire in declaration order, then destructors fire in reverse order as scopes unwind. The "stack" on the right is the live state. Press play.

Code
 
Live objects (stack)
Constructor/Destructor Log
The pattern to internalize: objects are constructed in declaration order and destroyed in reverse order. Constructors fire when execution reaches the declaration. Destructors fire when execution leaves the scope where the object was declared. Project 10 (Apologetics Library) makes this matter — when the Library is destroyed, every Book it owns is destroyed too, in the right order.

Why This Matters

Last chapter you wrote your first class. There were two pieces of clunkiness you might have noticed:

  1. Every object needed an init() call before you could use it. If you forgot, the private fields had garbage values.
  2. When you were done with an object, nothing happened. If the object held resources, they leaked.

This chapter solves both. Constructors make initialization automatic and unskippable. Destructors make cleanup automatic and unskippable. Together, they turn classes from "objects that mostly behave" into "objects that cannot misbehave at startup or shutdown."

The Constructor

class Account {
private:
    double balance;
    string owner;

public:
    Account(string n, double starting)
        : owner(n), balance(starting >= 0 ? starting : 0)
    {}

    void deposit(double amount) { /* ... */ }
};

// Used like this:
Account a("Maya", 100.0);

Two things to notice:

  • The constructor has the same name as the class and no return type.
  • The : owner(n), balance(...) is an initializer list — it initializes the fields directly. For class-type fields (like string) this is more efficient than assigning in the body.

The Destructor

class Account {
public:
    Account() { /* ... */ }
    ~Account() {
        cout << "Account for " << owner << " closed." << endl;
    }
};

Tilde (~) + class name. No parameters, no return type. Fires automatically when the object goes out of scope. You usually won't have much to do in the destructor for Phase 2 classes — but next chapter (pointers + new/delete) it becomes essential.

Composition: One Class Containing Another

class Library {
private:
    Book books[100];
    int book_count;
    Member members[50];
    int member_count;
public:
    Library() : book_count(0), member_count(0) {}
    void add_book(Book b);
    void add_member(Member m);
};

A Library owns Books and Members. The Library is the only thing that creates books or members; outside code has to ask politely. This is composition — one class composed of others — and it's the most common way OO systems are built.

Coach's Note — "Composition over inheritance" is the modern engineering watchword. When you can solve a problem by having one class contain another, prefer that to making one class inherit from another. Composition is more flexible, harder to misuse, and easier to reason about.

Const Methods

A method marked const promises not to modify the object:

double get_balance() const { return balance; }

Getters and queries should be const. Setters and mutators cannot be. This matters because of const references — a function taking const Account& can only call const methods. Get in the habit now; it pays off in Chapter 12.

This Week's Project

You're ready for Project 10: Apologetics Library. A small library of real apologetics resources — Mere Christianity, Confessions, The Reason for God, etc. — with Book and Member classes and a Library container. Constructors. Destructors that print "library closed" summaries. Real data with real authors.

Check Your Reps

Constructors & Destructors — Quick Check

Question 1 of 4
In what order do destructors fire when main returns?
int main() {
    Book a("X");
    Book b("Y");
    Book c("Z");
    return 0;
}
Why: Stack discipline: last-in, first-out. Objects are destroyed in the reverse order they were constructed. c dies first (it was the last constructed), then b, then a.
Question 2 of 4
You write only this constructor on a class:
class Book {
public:
    Book(string title) : title_(title) {}
private:
    string title_;
};
Why: Once you define any constructor, the compiler stops generating the default one. Book b; requires Book::Book() — which doesn't exist here. You'd either need to add an explicit Book() default constructor, or construct with arguments: Book b("Confessions");.
Question 3 of 4
What's the right signature for a getter that returns the title and is callable on a const Book reference?
Why: The const after the parameter list (get_title() const) is what makes the method callable on a const object. It's a promise to the compiler: this method will not modify the object. Returning const string is technically valid but unusual.
Question 4 of 4
The destructor in Phase 2 classes is often empty. Why does it matter to define one anyway?
Why: For Phase 2 classes whose fields are all stack-allocated, an empty destructor adds nothing — the compiler will generate one for you. Once Chapter 11 introduces new/delete, the destructor is where you free heap memory. Without it, every object leaks. Chapter 12 adds another reason: virtual destructors for polymorphic classes.
YOU FINISHED. NICE WORK.

← WEEK 9: CLASSES   ·   WEEK 11: POINTERS →