Skip to content
Theory · Classes and OOP · lesson 2 of 13

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:

C++
#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: Temperature t{20};. Temperature t = 20; compiles too: a one-parameter constructor converts 20 to a Temperature implicitly, and explicit before the constructor forbids that.

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 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:

C++
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:

C++
class Segment {
public:
    Segment(int start, int length)
        : start_(start), end_(start_ + length) {}  // ✗
    int end() { return end_; }

private:
    int end_;
    int start_;
};
TEXT
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).