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:
#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'; } }
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:
#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'; }
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:
#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(); }
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:
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 }
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:
#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"); }
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:
#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_; }
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.
3/4 -5/2 error: zero denominator