Constructors, destructors, and lifetime
Classes and object lifetime13 min readLesson 2 of 4
2.1 When an object begins and ends
An object's lifetime starts when its constructor finishes and ends when its destructor is called. The constructor's job is to leave the object in a state where every member function can run safely. The destructor's job is to release anything the object acquired.
C++ calls both automatically. A local object is destroyed at the end of its scope, a member is destroyed with its containing object, and a heap object is destroyed by delete. You write the constructor and destructor once, and the compiler inserts the calls at every exit path.
#include <iostream>
#include <string>
class Book {
public:
Book(std::string title, int pages) : title_(title), pages_(pages) {}
void show() const { std::cout << title_ << " has " << pages_ << " pages\n"; }
private:
std::string title_;
int pages_;
};
int main() {
Book book("C++ Notes", 240);
book.show();
return 0;
}Output
C++ Notes has 240 pages
2.1.1 The member initializer list
The text between : and the constructor body is the member initializer list. Members are constructed there, before the body runs. A member that appears only in the body was first default-constructed and then assigned, which costs work and can be impossible.
const members and reference members must be initialized in the list because they cannot be assigned later. Members are initialized in declaration order, whatever order the list uses. Compilers warn when the two orders differ, and reading a member that has not been initialized yet is undefined behavior.
#include <iostream>
#include <string>
class Ticket {
public:
Ticket(std::string owner, int seat) : owner_(owner), seat_(seat) {}
void show() const { std::cout << owner_ << " seat " << seat_ << "\n"; }
private:
std::string owner_;
const int seat_;
};
int main() {
Ticket ticket("Ravi", 14);
ticket.show();
return 0;
}Output
Ravi seat 14
2.1.2 Default, deleted, and explicit constructors
A class with no user-declared constructor gets a generated default constructor. Declaring any constructor removes that generated version, so a class that needs both forms writes ClassName() = default; or provides its own no-argument constructor.
= delete removes a function from the overload set. Session() = delete; prevents an object from being created without arguments, and the compiler reports the attempt at the call site. The explicit keyword on a one-argument constructor blocks implicit conversions such as Session s = 7;, while still allowing Session s(7);.
#include <iostream>
class Session {
public:
Session() = delete;
explicit Session(int id) : id_(id) {}
int id() const { return id_; }
private:
int id_;
};
int main() {
Session session(7);
std::cout << "Session " << session.id() << "\n";
return 0;
}Output
Session 7
2.1.3 Destructors and scope
A destructor has the class name with a leading ~, takes no parameters, and has no return type. It runs when an object's lifetime ends. Local objects are destroyed in reverse order of construction, and a member is destroyed after the destructor body of its containing object.
A class that owns a resource releases it in the destructor. A class that owns nothing can often omit the destructor entirely, because its members clean themselves up. Declaring a destructor also affects the copy and move operations the compiler generates, which matters for classes that manage memory.
#include <iostream>
#include <string>
class Marker {
public:
explicit Marker(std::string name) : name_(name) {
std::cout << "open " << name_ << "\n";
}
~Marker() { std::cout << "close " << name_ << "\n"; }
private:
std::string name_;
};
int main() {
Marker outer("outer");
{ Marker inner("inner"); }
std::cout << "back in main\n";
return 0;
}Output
open outer open inner close inner back in main close outer
2.1.4 Resource acquisition is initialization
RAII ties a resource to an object's lifetime. The constructor acquires the resource, and the destructor releases it. A std::string acquires character storage in its constructor and frees it in its destructor. A file stream opens a file and closes it the same way.
The pattern works on every exit path, including an early return and an exception. That is why the standard library uses it for memory, files, and locks. A class that allocates memory with new needs a destructor that calls delete, plus decisions about copying and moving. Later lessons replace most raw new with std::unique_ptr, which applies RAII for you.
#include <cstddef>
#include <iostream>
class Buffer {
public:
explicit Buffer(std::size_t size) : size_(size), data_(new int[size]()) {}
~Buffer() { delete[] data_; }
void set(std::size_t i, int value) { if (i < size_) data_[i] = value; }
int get(std::size_t i) const { return i < size_ ? data_[i] : -1; }
private:
std::size_t size_;
int* data_;
};
int main() {
Buffer buffer(3);
buffer.set(0, 10);
buffer.set(2, 30);
std::cout << buffer.get(0) << " " << buffer.get(2) << "\n";
return 0;
}Output
10 30
2.1.5 Temporaries and return values
A temporary object is created by an expression and destroyed at the end of the full expression that created it. print(Point(1, 2)); constructs a Point, passes it to print, and destroys it before the next statement.
Returning an object by value is safe. C++17 guarantees copy elision when a function returns a prvalue, so return Point(x, y); constructs the result directly in the caller's storage. Returning a reference to a local is still wrong, because the local dies at the return. Prefer values for new objects and references for objects that already exist.
2.1.6 Common mistakes
- Assigning members in the constructor body when the initializer list would do.
- Writing the initializer list in a different order from the member declarations.
- Forgetting that declaring a constructor removes the generated default constructor.
- Copying an object that owns a raw resource, which leads to a double delete.
- Throwing from a destructor. A destructor that runs during unwinding must not throw another exception.
- Expecting
Session session = 7;to compile when the constructor isexplicit.
2.1.7 Practice
- Write a
Pointclass with an initializer-list constructor and ashow()function. - Add a
const int id_member and initialize it from a constructor parameter. - Print a message from a constructor and from a destructor, then create an object inside a nested block.
- Write a class that allocates an array with
new[]and frees it withdelete[]. - Explain why
Session s = 7;fails when the constructor isexplicit.
