What you'll learn
Constructors establish valid objects; destructors finish their lifetimes. RAII connects those two events to every resource—files, locks, memory, sockets, and handles—so cleanup follows normal scope rules instead of scattered manual calls.
By the end, you'll be able to:
- Write default, parameterized, delegating, and explicit constructors
- Initialize members in declaration order with initializer lists
- Explain deterministic destruction and composition order
- Design a small scope-bound resource owner with RAII
Constructors establish validity
A constructor has the class name and no return type. It transforms raw inputs into a valid object or reports failure; callers should never receive a half-initialized instance. Default member initializers provide sensible fallbacks, while a delegating constructor routes several entry points through one validation path.
#include <stdexcept>
class Timer {
public:
Timer() : Timer{60} {} // delegate to the validating constructor
explicit Timer(int seconds) : remaining_{seconds} {
if (seconds < 0) {
throw std::invalid_argument{"negative duration"};
}
}
int remaining() const { return remaining_; }
private:
int remaining_{};
};Key idea
initialize() call.Initializer lists and order
Members are initialized before the constructor body. An initializer list constructs them directly; assigning inside the body would first default-construct and then assign. Actual initialization follows member declaration order, not the textual order in the list.
#include <string>
#include <utility>
class Session {
public:
Session(std::string user, int retry_limit)
: user_{std::move(user)}, retry_limit_{retry_limit} {}
private:
std::string user_; // initialized first
int retry_limit_{3}; // initialized second
};- References and const members must be initialized, not assigned later
- Base classes are initialized before members
- Members are initialized in their declaration order
- The constructor body runs only after bases and members exist
Watch out
Deterministic destruction
A destructor named ~Type() runs when an object's lifetime ends. Automatic objects are destroyed when scope exits, including exits caused by return or an exception. Members are destroyed in reverse declaration order, then base subobjects are destroyed.
#include <iostream>
#include <string>
class Trace {
public:
explicit Trace(std::string name) : name_{std::move(name)} {
std::cout << "open " << name_ << '\n';
}
~Trace() { std::cout << "close " << name_ << '\n'; }
private:
std::string name_;
};
int main() {
Trace outer{"outer"};
{ Trace inner{"inner"}; }
}RAII: lifetime owns cleanup
Resource Acquisition Is Initialization means a successfully constructed object owns a resource and its destructor releases it. The object's lifetime becomes the one cleanup path. Standard streams, containers, smart pointers, and lock guards already follow this model.
#include <fstream>
#include <iostream>
#include <stdexcept>
#include <string>
int main() {
std::ifstream input{"notes.txt"};
if (!input) throw std::runtime_error{"cannot open notes.txt"};
std::size_t count{};
for (std::string line; std::getline(input, line); ) {
++count;
}
std::cout << count << '\n';
} // input closes here on every exit pathTip
A scope-bound handle
Platform APIs often return a raw handle with a matching release function. Wrap that pair immediately. The wrapper deletes copying, supports an empty state, and performs cleanup in the destructor. Later, move operations can transfer the handle safely.
#include <cstdio>
#include <stdexcept>
class FileHandle {
public:
FileHandle(const char* path, const char* mode)
: file_{std::fopen(path, mode)} {
if (!file_) throw std::runtime_error{"open failed"};
}
~FileHandle() {
if (file_) std::fclose(file_);
}
FileHandle(const FileHandle&) = delete;
FileHandle& operator=(const FileHandle&) = delete;
std::FILE* get() const { return file_; }
private:
std::FILE* file_{};
};Explicit, defaulted, and deleted operations
A one-argument constructor can become an implicit conversion. Mark it explicitunless that conversion is clearly part of the type's meaning. = default asks for normal generated behavior explicitly; = delete makes a forbidden operation fail at compile time with a clear contract.
| Syntax | Meaning |
|---|---|
explicit Id(int) | Direct construction allowed; implicit conversion rejected |
Widget() = default | Request the compiler's normal implementation |
Widget(const Widget&) = delete | Copy construction is forbidden |
~Widget() noexcept | Destruction promises not to emit an exception |
Destructors should not throw
Recap & quick check
Key takeaways
- Constructors should establish every invariant or fail without producing an object.
- Initializer lists construct members directly, and actual order follows declarations.
- Destruction is deterministic and reverses construction order.
- RAII turns object lifetime into a reliable cleanup path for every exit route.
- explicit, = default, and = delete make construction and special-operation policy visible.
Quick check
1. When does a constructor body run?
2. What controls member initialization order?
3. What is RAII's central connection?
4. Why mark a one-argument constructor explicit?
Next: Module 11 — Copying, Moving & the Rule of Zero, where object ownership becomes predictable value behavior.