Урок №117. Список инициализации членов класса

  Юрий  | 

    | 

  Обновл. 12 Июн 2019  | 

 16981

 ǀ   3 

В этом уроке мы рассмотрим то, как инициализировать переменные-члены класса с помощью списка инициализации, а также нюансы и особенности, которые при этом могут возникнуть.

Списки инициализации членов класса

В предыдущем уроке мы инициализировали члены нашего класса в конструкторе через оператор присваивания:

Сначала создаются m_value1, m_value2 и m_value3. Затем выполняется тело конструктора, где этим переменным присваиваются значения. Аналогичен код в не объектно-ориентированном C++:

Хотя в плане синтаксиса языка C++ вопросов никаких нет — всё корректно, но было бы более эффективно, если бы мы использовали инициализацию, а не присваивание после объявления.

Как мы уже знаем из предыдущих уроков, некоторые типы данных (например, const и ссылки) должны быть инициализированы сразу. Рассмотрим следующий пример:

Аналогичен код в не объектно-ориентированном C++:

Для решения этой проблемы в C++ добавили метод инициализации переменных-членов класса через список инициализации членов (или ещё «список инициализаторов членов»), вместо присваивания им значений после объявления. Не путайте этот список с аналогичным списком инициализаторов, который используется для инициализации массивов.

Из урока №28 мы уже знаем, что инициализировать переменные можно тремя способами: через копирующую, прямую или uniform (C++11) инициализации:

Использование списка инициализации почти идентично выполнению прямой инициализации (или uniform инициализации в C++11).

Чтобы было понятнее, рассмотрим пример. Вот код с присваиванием значений переменным-членам класса в конструкторе:

Теперь давайте перепишем этот код, но уже с использованием списка инициализации:

Результат выполнения программы выше:

Values(3, 4.5, d)

Список инициализации членов находится сразу же после параметров конструктора. Он начинается с двоеточия (:), а затем значение для каждой переменной указывается в круглых скобках. Больше не нужно выполнять операции присваивания в теле конструктора. Также обратите внимание, список инициализаторов не заканчивается точкой с запятой.

Можно также добавить возможность caller-у передавать значения для инициализации:

Результат выполнения программы выше:

Values(3, 4.5, d)

Мы можем использовать параметры по умолчанию для предоставления значений по умолчанию, если пользователь их не предоставил. Например, класс, который имеет константную переменную-член:

Это работает, поскольку нам разрешено инициализировать константные переменные (но не присваивать им значения после объявления!).

Правило: Используйте списки инициализации, вместо операций присваивания, для инициализации переменных-членов вашего класса.

uniform инициализация в C++11


В C++11 вместо прямой инициализации можно использовать uniform инициализацию:

Настоятельно рекомендуется использовать этот синтаксис (даже если вы не используете константы или ссылки в качестве переменных-членов вашего класса), поскольку списки инициализации необходимы при композиции и наследовании (это рассмотрим несколько позже).

Правило: Используйте uniform инициализацию вместо прямой инициализации в C++11.

Инициализация массивов в классе

Рассмотрим класс с массивом в качестве переменной-члена:

До C++11 мы могли только обнулить массив через список инициализации:

Однако, в C++11 вы можете полностью инициализировать массив, используя uniform инициализацию:

Инициализация переменных-членов, которые являются классами


Список инициализации членов также может использоваться для инициализации членов, которые являются классами:

Результат выполнения программы выше:

A 6
B 7

При создании переменной b вызывается конструктор B(int) со значением 7. До того, как тело конструктора выполнится, инициализируется m_a, вызывая конструктор A(int) со значением 6. Таким образом, выведется A 6. Затем управление возвратится обратно к конструктору B, и тогда уже он выполнится и выведется B 7.

Использование списков инициализации

Если список инициализации помещается на той же строке, что и имя конструктора, то лучше всё разместить в одной строке:

Если список инициализаторов не помещается в строке с именем конструктора, то на следующей строке (используя перенос) инициализаторы должны быть с отступом:

Если все инициализаторы не помещаются в одной строке, то вы можете выделить для каждого инициализатора отдельную строку:

Порядок выполнения в списке инициализации


Удивительно, но переменные в списке инициализаторов не инициализируются в том порядке, в котором они указаны. Вместо этого они инициализируются в том порядке, в котором объявлены в классе. Поэтому следует соблюдать следующие рекомендации:

   Не инициализируйте переменные-члены таким образом, чтобы они зависели от других переменных-членов, которые инициализируются первыми (другими словами, убедитесь, что все ваши переменные-члены правильно инициализируются, даже если порядок в списке инициализации отличается).

   Инициализируйте переменные в списке инициализации в том порядке, в котором они объявлены в классе.

Заключение

Списки инициализации членов позволяют инициализировать члены, а не присваивать им значения. Это единственный способ инициализации констант и ссылок, которые являются переменными-членами вашего класса. Во многих случаях использование списка инициализации может быть более результативным, чем присваивание значений переменным-членам в теле конструктора. Списки инициализаторов работают как с переменными фундаментальных типов данных, так и с членами, которые сами являются классами.

Тест

Напишите класс с именем RGBA, который содержит 4 переменные-члены типа std::uint8_t (#include cstdint для доступа к типу std::uint8_t):

   m_red;

   m_green;

   m_blue;

   m_alpha.

Присвойте 0 в качестве значения по умолчанию для m_red, m_green и m_blue, и 255 для m_alpha. Создайте конструктор со списком инициализации членов, который позволит пользователю передавать значения для m_red, m_blue, m_green и m_alpha. Напишите функцию print(), которая будет выводить значения переменных-членов.

Подсказка: Если функция print() работает некорректно, то убедитесь, что вы конвертировали uint8_t в int.

Следующий код функции main():

Должен производить следующий результат:

r=0 g=135 b=135 a=255

Ответ

Оценить статью:

Звёзд: 1Звёзд: 2Звёзд: 3Звёзд: 4Звёзд: 5 (64 оценок, среднее: 5,00 из 5)
Загрузка...

Комментариев: 3

  1. Аватар Ануар:

    спасибо большое за статьи, благодаря вам, я наконец начал понимать ООП!

    1. Юрий Юрий:

      Спасибо, что читаете 🙂

    2. Аватар Александр:

      Фейнман говорил о квантовой физике… думаю, применимо и к ООП:
      — Если Вам кажется, что Вы поняли квантовую физику, значит Вы ничего не понимаете в квантовой физике
      🙂

Добавить комментарий

Ваш E-mail не будет опубликован. Обязательные поля помечены *