Constructors and the member initializer list
A private field cannot be set from outside: the line t.celsius_ = 20; does not compile for an object with a private field. The value has to be passed when the object is created, and a class declares a constructor for that:
#include <iostream>
class Temperature {
public:
Temperature(int celsius) : celsius_(celsius) {}
int fahrenheit() { return celsius_ * 9 / 5 + 32; }
private:
int celsius_;
};
int main() {
Temperature t(20);
std::cout << t.fahrenheit() << '\n'; // 68
}Constructors
A constructor is a method named after its class, with no return type. It runs on its own when an object is created: the line Temperature t(20); calls the constructor with the argument 20. The arguments can also go in curly braces, as in Temperature t{20};.
The default constructor
A constructor with no parameters is called a default constructor, and the declaration Temperature t; calls it. If a class has no constructors at all, the compiler creates a default constructor by itself. But once a constructor with parameters is declared, the line Temperature t; stops compiling until a constructor without parameters is declared explicitly.
The word explicit
The line Temperature t = 20; compiles too. A one-parameter constructor converts the number 20 into a Temperature object implicitly. The word explicit before the constructor forbids that conversion: the object can then be created only as t(20) or t{20}.
The initializer list
The part : celsius_(celsius) between the constructor's header and its body is the member initializer list. It gives the fields their initial values before the constructor body starts running, which is why the body here is empty. If a field is an object of a class, the parentheses hold the arguments for its constructor. A const field cannot be changed in the constructor body, so it is set right here or in its declaration.
A field can also get a value in its own declaration, like count_ below. That value is used when the constructor's list does not name the field, and an entry in the list replaces it:
class Counter {
public:
Counter() {}
Counter(int start) : count_(start) {}
int value() { return count_; }
private:
int count_ = 0;
};For Counter a; the empty default constructor runs, and a.value() returns 0. For Counter b(5); the list sets count_ to 5, and the = 0 is not used.
The class sets the order of initialization, not the list. Fields are initialized in the order they are declared, even when the list puts them in a different order. The compiler warns about such a mismatch, so the list is written in the same order as the fields.
Common mistake
One field is initialized from another one that is declared below it:
class Segment {
public:
Segment(int start, int length)
: start_(start), end_(start_ + length) {} // ✗
int end() { return end_; }
private:
int end_;
int start_;
};warning: field 'start_' will be initialized after field 'end_' [-Wreorder-ctor]The field end_ is declared first and is initialized first, while start_ has no value yet at that moment. Reading start_ here is undefined behavior, even though the compiler only warns. Declare start_ above end_, or compute end_ from the parameters: end_(start + length).
Key points
- A constructor runs on its own when an object is created and gives the private fields their initial values.
- Once a constructor with parameters is declared, the default constructor no longer appears by itself.
- The initializer list sets the fields before the constructor body starts.
- Fields are initialized in the order they are declared in the class, not in the order of the list.
Subscribe to take the course
The course is free. Sign in with Telegram and subscribe to the channel to open every lesson.