Перейти к содержимому
Теория · Классы и ООП · урок 2 из 13

Конструкторы и список инициализации

Закрытое поле нельзя задать снаружи: строка t.celsius_ = 20; для объекта с закрытым полем не скомпилируется. Значение нужно передать в момент создания объекта, и для этого в классе объявляют конструктор (constructor):

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
}

Конструктор

Конструктор — метод с именем класса и без типа результата. Он выполняется сам, когда объект создаётся: строка Temperature t(20); вызывает конструктор с аргументом (argument) 20. Аргументы можно передать и в фигурных скобках: Temperature t{20};. Temperature t = 20; тоже компилируется: конструктор с одним параметром (parameter) неявно преобразует 20 в Temperature, а слово explicit перед конструктором это запрещает.

Конструктор без параметров называют конструктором по умолчанию (default constructor), его вызывает объявление Temperature t;. Если в классе нет ни одного конструктора, компилятор создаёт конструктор по умолчанию сам. Но стоит объявить конструктор с параметрами, и строка Temperature t; перестанет компилироваться, пока конструктор без параметров не объявят явно.

Список инициализации

Запись : celsius_(celsius) между заголовком конструктора и его телом — список инициализации (member initializer list). Он задаёт полям начальные значения до того, как начнёт выполняться тело конструктора, поэтому тело здесь пустое. Если поле — объект класса, в скобках передают аргументы его конструктора. Поле с const в теле конструктора уже не изменить, поэтому его задают именно здесь или в объявлении.

Поле может получить значение и в своём объявлении, как count_ ниже. Это значение используется, когда конструктор не упомянул поле в списке, а запись в списке его заменяет:

C++
class Counter {
public:
    Counter() {}
    Counter(int start) : count_(start) {}
    int value() { return count_; }

private:
    int count_ = 0;
};

Для Counter a; выполняется пустой конструктор по умолчанию, и a.value() возвращает 0. Для Counter b(5); список задаёт count_ значение 5, и = 0 не используется.

Порядок инициализации задаёт класс, а не список: поля инициализируются в порядке объявления, даже если в списке они записаны иначе. Компилятор предупреждает о таком расхождении, поэтому список пишут в том же порядке, что и поля.

Частая ошибка

Одно поле инициализируют через другое, объявленное ниже:

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]

Поле end_ объявлено первым и инициализируется первым, а start_ в этот момент ещё не получило значения. Чтение start_ здесь — неопределённое поведение (undefined behavior), хотя компилятор только предупреждает. Объявите start_ выше end_ или вычисляйте end_ из параметров: end_(start + length).