Constructors and Destructors

A constructor runs when an object is created and makes sure it starts in a valid state; a destructor runs when the object is destroyed and releases whatever it holds. Together they're what make RAII work.

Constructors

A constructor has the class's name and no return type. A member initialiser list (after the colon) initialises members directly:

C++
#include <iostream>
#include <stdexcept>
#include <string>

class Temperature {
public:
    explicit Temperature(double celsius) : celsius_(celsius) {
        if (celsius < -273.15) throw std::invalid_argument("below absolute zero");
    }
    double celsius() const { return celsius_; }
    double fahrenheit() const { return celsius_ * 9 / 5 + 32; }
private:
    double celsius_;
};

int main() {
    Temperature t{21.5};
    std::cout << t.celsius() << " C = " << t.fahrenheit() << " F\n";
    try {
        Temperature impossible{-300};
    } catch (const std::invalid_argument& e) {
        std::cout << "rejected: " << e.what() << '\n';
    }
}
Output
21.5 C = 70.7 F
rejected: below absolute zero

If a constructor can't create a valid object, it throws — so a Temperature that exists is always valid. Initialise members in the list rather than assigning in the body: it's more efficient, and it's the only way for const members, references and members without a default constructor.

Several constructors

Constructors can be overloaded, and one can delegate to another:

C++
#include <iostream>
#include <string>

class Colour {
public:
    Colour(int r, int g, int b) : r_(r), g_(g), b_(b) {}
    explicit Colour(int grey) : Colour(grey, grey, grey) {}   // delegating constructor
    Colour() : Colour(0) {}                                    // default: black

    std::string css() const {
        return "rgb(" + std::to_string(r_) + ", " + std::to_string(g_) + ", " + std::to_string(b_) + ")";
    }
private:
    int r_, g_, b_;
};

int main() {
    Colour teal{0, 128, 128};
    Colour grey{200};
    Colour black;
    std::cout << teal.css() << ' ' << grey.css() << ' ' << black.css() << '\n';
}
Output
rgb(0, 128, 128) rgb(200, 200, 200) rgb(0, 0, 0)

Default member initialisers

Values given where members are declared apply to every constructor that doesn't set them — the simplest way to avoid uninitialised members:

C++
#include <iostream>
#include <string>

class Settings {
public:
    Settings() = default;                                   // use all the defaults
    explicit Settings(int volume) : volume_(volume) {}
    void print() const { std::cout << theme_ << ", volume " << volume_ << ", subtitles " << subtitles_ << '\n'; }
private:
    std::string theme_ = "light";
    int volume_ = 50;
    bool subtitles_ = false;
};

int main() {
    Settings().print();
    Settings(80).print();
}
Output
light, volume 50, subtitles 0
light, volume 80, subtitles 0

explicit

A constructor with one parameter also acts as an implicit conversion. That can be surprising — here a plain number silently becomes a Temperature. Marking the constructor explicit forbids that:

C++
class Temperature {
public:
    explicit Temperature(double c) : c_(c) {}
    double c_;
};

void show(Temperature) {}

int main() {
    show(Temperature{20});   // fine: explicit construction
    show(20);                // error: no implicit conversion
}
Compiler error
main.cpp:11:5: error: no matching function for call to 'show'

Make single-argument constructors explicit unless the conversion is genuinely natural (as with std::string from a string literal).

Destructors

A destructor is named ~ClassName, takes no arguments, and runs automatically when the object's lifetime ends. Members are destroyed after the destructor body, in reverse order of declaration:

C++
#include <iostream>
#include <string>

class Logger {
public:
    explicit Logger(std::string name) : name_(std::move(name)) { std::cout << name_ << ": opened\n"; }
    ~Logger() { std::cout << name_ << ": closed\n"; }
    void log(const std::string& msg) { std::cout << name_ << ": " << msg << '\n'; }
private:
    std::string name_;
};

int main() {
    Logger app{"app"};
    {
        Logger db{"db"};
        db.log("query run");
    }
    app.log("still running");
}
Output
app: opened
db: opened
db: query run
db: closed
app: still running
app: closed

Most classes don't need a destructor at all: if every member cleans up after itself (strings, vectors, smart pointers), the compiler's automatic destructor does everything (the rule of zero, lesson 30).

Order of construction

Members are always initialised in the order they're declared in the class, not the order they appear in the initialiser list. Compilers warn when the two differ, because using one member to initialise another in the wrong order reads garbage:

C++
#include <string>

class Range {
public:
    Range(int start, int length) : end_(start + length), start_(start) {}
    int start_;
    int end_;
};

int main() {
    Range r{5, 10};
    return r.end_;
}
Compiler warnings
main.cpp:5:36: warning: field 'end_' will be initialized after field 'start_' [-Wreorder-ctor]

Try it yourself

Write a Fraction class whose constructor reduces the fraction and rejects a zero denominator.

Try it yourself · main.cpp
Edit the code, copy it into main.cpp, then compile and run it with c++ -std=c++20 -Wall main.cpp -o main && ./main. How to set up a C++ compiler
Output
3/4
-5/2
error: zero denominator