Constructors, Destructors, & Encapsulation
"Better is the end of a thing than its beginning." — Ecclesiastes 7:8
Why This Matters
Last chapter you wrote your first class. There were two pieces of clunkiness you might have noticed:
- Every object needed an
init()call before you could use it. If you forgot, the private fields had garbage values. - 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 (likestring) 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.
Constructors & Destructors — Quick Check
main returns?int main() {
Book a("X");
Book b("Y");
Book c("Z");
return 0;
} c dies first (it was the last constructed), then b, then a. class Book {
public:
Book(string title) : title_(title) {}
private:
string title_;
}; 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");. const Book reference?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. 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.