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

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

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

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); вызывает конструктор с аргументом 20. Аргументы можно передать и в фигурных скобках, как в Temperature t{20};.

Конструктор по умолчанию

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

Слово explicit

Компилируется и запись Temperature t = 20;. Конструктор с одним параметром неявно преобразует число 20 в объект Temperature. Слово explicit перед конструктором запрещает такое преобразование: тогда объект создают только записями t(20) и t{20}.

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

Запись : celsius_(celsius) между заголовком конструктора и его телом — список инициализации. Он задаёт полям начальные значения до того, как начнёт выполняться тело конструктора, поэтому тело здесь пустое. Если поле — объект класса, в скобках передают аргументы его конструктора. Поле с 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_ здесь — неопределённое поведение, хотя компилятор только предупреждает. Объявите start_ выше end_ или вычисляйте end_ из параметров: end_(start + length).

Главное

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

Подпишись, чтобы пройти курс

Курс бесплатный. Войди через Telegram и подпишись на канал — откроются все уроки.

Войти другим способом