Списки инициализации в C++: хороший, плохой, злой

В этой статье я бы хотел рассказать о том, как работают списки инициализации (braced initializer lists) в C++, какие проблемы они были призваны решать, какие проблемы, в свою очередь, вызвали и как не попасть в просак.
Первым делом предлагаю почувствовать себя компилятором (или language lawyer-ом) и понять, компилируются ли следующие примеры, почему, и что они делают:
std::vector v1; std::vector v2(5); std::vector v3(); std::vector v4; std::vector v5 = 5;
Современный C++ — безопасный язык, я никогда не выстрелю себе в ногу:
std::vector x( ); std::vector y < >;
Больше скобочек богу скобочек!
// Почему их тут пять, скомпилируется ли программа и почему? std::vector> v1>>>>;
Если один конструктор не подходит, мы берем второй, правильно?
struct T<>; struct S < S(std::initializer_list); S(double, double); S(T, T); >; int main() < S, T<>>; // Работает ли вот так? S; // а так? >
Almost Always Auto, говорили они. Это повышает читабельность, говорили они:
auto x = ; // какой тут тип у x? auto y; // а у y? // вы уверены? попробуйте другую версию вашего компилятора
Привет из древних времен:
struct S < std::vectora, b; >; struct T < std::arraya, b; >; int main() < T t1, >; T t2; T t3; S s1, >; S s2; S s3; >
Все понятно? Или ничего не ясно? Добро пожаловать под кат.
Disclaimers
- Эта статья ознакомительная, не претендует на полноту и часто будет жертвовать корректностью в угоду понятности. С другой стороны, у читателя предполагается базовое знание C++.
- Я пытался придумывать разумные переводы на русский для англоязычных терминов, но с некоторыми я потерпел полное фиаско. Синтаксические конструкции вида я буду называть braced-init-lists, тип из стандартной библиотеки — std::initializer_list , а вид инициализации, когда мы пишем как-то так: int x — это list-init, также известная как uniform initialization syntax, или универсальный синтаксис инициализации.
Attention!
Первым делом обращу внимание на важное наблюдение. Даже если вы из всей статьи вынесете только его, а дальше читать станет лень, моя миссия здесь будет исполнена.
Итак, braced-init-lists (штуки с фигурными скобками, , uniform initialization syntax) и std::initializer_list — разные вещи! Они сильно связаны, между ними происходят всякие тонкие взаимодействия, но любое из них вполне может существовать без другого.
Но сначала — немного предыстории.
Unicorn initialization syntax

В C++98 (и его bugfix-update, C++03) существовало достаточно проблем и непоследовательностей, связанных с инициализацией. Вот некоторые из них:
- Из C пришел синтаксис инициализации переменных (в том числе, массивов и структур) с использованием фигурных скобок, но он не очень хорошо взаимодействовал с возможностями C++ (например, инициализация структур не была доступна для C++-классов)
- Часто хочется соорудить какой-нибудь контейнер (например, std::vector ) из заранее известных элементов — в языке не было встроенной возможности для этого, а библиотечные решения ( Boost.Assign ) не отличались изящностью синтаксиса, были не бесплатны с точки зрения скорости работы и не слишком хорошо влияли на время компиляции
- При инициализации примитивных типов легко случайно потерять информацию при сужающем преобразовании (narrowing conversion) — например, случайно присвоить double в int
- Most vexing parse, которым любят пугать начинающих C++-ников.
Поэтому во время разработки C++11 родилась такая идея: давайте мы дадим возможность проинициализировать что угодно с помощью фигурных скобок:
- Для случаев, где это применимо в C, новый синтаксис будет работать так же, только лучше
- Сужающие преобразования при этом мы запретим
- А если мы пытаемся проинициализировать класс с конструкторами, то мы и конструктор сможем вызывать, с переданными параметрами
Pitfalls
Казалось бы, на этом можно и закончить: инициализация контейнеров должна получиться сама собой, ведь в C++11 появились еще и шаблоны с переменным числом параметров, так что если мы напишем variadic-конструктор… на самом деле, нет, так не получится:
- Такой конструктор должен быть шаблоном, что часто нежелательно
- Придется инстанцировать конструкторы со всевозможным числом параметров, что приведет к раздуванию кода и замедлению компиляции
- Эффективность инициализации, например, для std::vector -а будет все равно не идеальная
Для решения этих проблем придумали std::initializer_list — «магический класс», который представляет собой очень легкую обертку для массива элементов известного размера, а так же умеет конструироваться от braced-init-list-а.
Почему же он «магический»? Как раз по описанным выше причинам его невозможно эффективно сконструировать в пользовательском коде, поэтому компилятор создает его специальным образом.
Зачем же он нужен? Главным образом, чтобы пользовательские классы могли сказать: «я хочу конструироваться от braced-init-list-а элементов такого-то типа», и им не требовался бы для этого шаблонный конструктор.
(Кстати, к этому моменту должно стать понятно, что std::initializer_list и braced-init-list это разные понятия)
Теперь-то все хорошо? Мы просто добавим в наш контейнер конструктор вида vector(std::initializer_list) и все заработает? Почти.
Рассмотрим такую запись:
std::vector v;
Что имелось в виду, v(5) или v() ? Другими словами, хотим ли мы сконструировать вектор из 5 элементов, или из одного элемента со значением 5 ?
Для решения этого конфликта разрешение перегрузок (overload resolution, выбор нужной функции по переданным аргументам) в случае list-initialization происходит в два этапа:
- Сначала рассматриваются только конструкторы с единственным параметром типа std::initializer_list (это один из главных моментов, когда компилятор таки генерирует std::initializer_list по содержимому фигурных скобочек). Разрешение перегрузок происходит между ними.
- Если ни один конструктор не подходит, то дальше все как обычно — разворачиваем braced-init-list в список аргументов и проводим разрешение перегрузок среди всех доступных конструкторов.
Отметим, что конструктор, который проиграл на первом этапе, вполне может подойти на втором. Это объясняет пример с избытком скобочек для инициализации вектора из начала статьи. Для понятности удалим один из вложенных шаблонов, а также заменим std::vector на свой класс:
template struct vec < vec(std::initializer_list); >; int main() < vecv1>>; >
Под пункт 1 наш конструктор не подходит — >> не похож на std::initializer_list , потому что int нельзя проинициализировать с помощью > . Однако <> — вполне себе zero-initialization, поэтому конструктор принимается на втором шаге.
Забавно, однако, что сужающее преобразование не является достаточным поводом для того, чтобы выкинуть конструктор — в следующем примере первый конструктор принимается на первом шаге разрешения перегрузок, и потом вызывает ошибку компилятора. Хорошо это или плохо — я не знаю, для меня это просто удивительно.
struct S < S(std::initializer_list); S(double, double); >; int main() < S; >
Похожая проблема с довольно страшным результатом получается и в примере с вектором строк из начала статьи. К несчастью, у std::string есть конструктор, который трактует два переданных указателя как начало и конец строки. Последствия такого поведения для строковых литералов, очевидно, плачевны, при этом синтаксически запись выглядит довольно похоже на корректный вариант и вполне может появиться, например, в обобщенном коде.
Классы-агрегаты
Ну теперь-то все? Не совсем. Старый синтаксис инициализации структур, доставшийся нам от C, никуда не делся, и можно делать так:
struct A < int i, j; >; struct B < A a1, a2; >; int main() < B b1 = , >; B b2 = ; // brace elision B b3 = >; // clause omission >
Как видим, при иницализации агрегатов (грубо говоря, C-подобных структур, не путать с POD, POD — это про другое) можно и пропускать вложенные скобочки, и выкидывать часть инициализаторов. Все это поведение было аккуратно перенесено в C++.
Казалось бы, какой бред, зачем это в современном языке? Давайте хотя бы предупреждения компилятора будем на это выводить, подумали разработчики GCC и clang, и были бы правы, не будь std::array классом-агрегатом, содержащим внутри себя массив. Таким образом, предупреждение про выкидывание вложенных скобок по понятным причинам срабатывает на вот таком невинном коде:
int main() < std::arraya = ; >
Проблему эту GCC «решил» выключением соответствующего предупреждения в режиме -Wall , в clang-е же уже три года все по-прежнему.
Кстати, тот факт, что std::array — агрегат, не прихоть безумных авторов стандарта или ленивых разработчиков стандартных библиотек: достичь требуемой семантики этого класса просто невозможно средствами языка, не теряя в эффективности. Еще один привет от C и его странных массивов.
Возможно, большая проблема с классами-агрегатами — это не самое удачное взаимодействие с обобщенными функциями (в том числе) из стандартной библиотеки. На данный момент функции, которые конструируют объект из переданных параметров (например, vector::emplace_back или make_unique ), вызывают обычную инициализацию, не «универсальную». Вызвано это тем, что использование list-initialization не позволяет никаким нормальным способом вызвать «обычный» контруктор вместо принимающего std::initializer_list (примерно та же проблема, что и с инициализацией в не-шаблонном коде, только тут пользователь не может обойти ее вызовом другого конструктора). Работа в этом направлении ведется, но пока мы имеем то, что имеем.
Almost Always Auto
Как же braced-init-list-ы ведут себя в сочетании с выводом типов? Что будет, если я напишу auto x = ; auto y = ; ? Можно придумать несколько разумных стратегий:
- Запретить такую инициализацию вообще (в самом деле, что программист хочет этим сказать?)
- Вывести тип первой переменной как int , а второй вариант запретить
- Сделать так, чтобы и x, и y имели тип std::initializer_lits

Последний вариант нравится мне меньше всего (мало кому в реальной жизни заводить локальные переменные типа std::initializer_list ), но в стандарт С++11 попал именно он. Постепенно стало выясняться, что это вызывает проблемы у программистов (кто бы мог подумать), поэтому в стандарт добавили патч http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n3922.html, который реализует поведение №2… только в случае direct-list-initialization ( auto x ), а в случае copy-list-initialization ( auto x = ) оставляет все по-старому.
Я не могу это комментирвать. По-моему, это один из очень редких случаев, когда здравый смысл временно покинул авторов языка. Если у вас есть, что сказать по этому поводу, сообщите мне об этом в комментариях.
Промежуточные итоги
Хотя универсальный синтаксис инициализации и std::initializer_list — возможности языка, добавленные из благих и правильных побуждений, мне кажется, что из-за извечной необходимости в обратной совместимости и не всегда дальновидных решениях на ранних этапах вся ситуация вокруг них на данный момент излишне сложная, вымученная и не самая приятная для всех вовлеченных сторон — авторов стандарта, компиляторов, библиотек и прикладных разработчиков. Хотели как лучше, а получилось, как в известном комиксе:

В качестве примера возьмем, например, историю с [over.best.ics]/4.5, который сначала добавили в стандарт, потом, не подумав, удалили, как избыточный, а потом добавили обратно в измененном виде — как описание крайнего случая с пятью (!) условиями.
Тем не менее, возможность полезная и облегчающая жизнь, поэтому здесь я приведу небольшой и не претендующий на объективность список того, как не выстрелить себе в ногу:
- Потратьте некоторое время на то, чтобы ознакомиться с тем, что на самом деле происходит (я рекомендую прочитать параграф стандарта — на удивление понятный и не слишком зависимый от остальных)
- Не используйте std::initializer_list , кроме как в параметре конструктора
- Да и в параметре конструктора используйте, только если вы понимаете, что происходит (если не уверены — сконструируйтесь лучше от вектора, пары итераторов или range-а)
- Не используйте классы-агрегаты без крайней необходимости, напишите лучше конструктор, инициализирующий все поля
- Не используйте braced-init-list в сочетании с auto
- Прочитайте эту статью про то, что делать с пустыми списками инициализации (у меня руки чешутся ее перевести и запостить, может быть, вскоре займусь)
- И, как я уже писал в самом начале, имейте в виду, что braced-init-list и std::initializer_list — это разные концепции, весьма хитро взаимодействующие друг с другом
Давайте помечтаем
Тут я заканчиваю свое введение в существующее положение дел, и хочу вбросить помечтать, как все могло бы быть, если бы мы жили в идеальном мире.
Мне кажется, что переиспользовать фигурные скобки для создания std::initializer_list во время инициализации — ошибка дизайна языка. Я был бы очень рад, если бы вместо этого мы получили бы более явный и отдельный синтаксис (пусть и более уродливый, например, какие-нибудь странные скобки типа или встроенный интринзик вроде std::of(. ) ). То есть инициализируем вектор как-то так: std::vector> x = std::of(std::of(1, 2), std::of(3, 4));
Что бы это дало? Новый способ инициализации (с защитой от most vexing parse и сужающих преобразований) оказался бы отвязан от std::initializer_list , не потребовалось бы вводить отдельный шаг для разрешения перегрузок, ушла бы проблема с конструктором vector или vector , новый синтаксис инициализации можно было бы использовать в обобщенном коде безо всяких проблем.
Конечно, недостатки у такого подхода довольно серьезные: более уродливый синтаксис в простейших случаях и уход от цели сделать синтаксис более унифицированным с инициализацией в стиле C (к такой унификации я отношусь довольно скептически, но это тема для отдельного разговора).
Еще я недолюбливаю классы-агрегаты. Если оставить за скобками проблему с std::array , я не вижу достойного обоснования для существования такой большой и особенной возможности языка. Проблему с тем, что программистам не хочется писать тривиальные конструкторы для простых классов, можно было бы решить менее инвазивными способами, например, дать возможность генерировать конструктор, который бы инициализировал все поля по очереди:
struct S < int a, b; S(. ) = aggregate; >;
Заключение
Напоследок повторюсь еще раз, что я не претендую на 100% корректность или на истину в последней инстанции. Добро пожаловать в комментарии, если что-то осталось непонятным, или если есть что сказать по этой довольно специфической теме.
Блоки инициализации в Java — Часть 1

Привет! В данной статье мы расскажем самое-самое основное про блоки инициализации в Java. Но у этой статьи будет продолжение — часть 2, в которой будут описаны более сложные аспекты. Приятного прочтения!
Что такое инициализация
Для того, чтобы понять, что такое блоки инициализации, давайте сначала разберемся что такое инициализация. Кстати, этот вопрос Вам могут задать на собеседовании! Так что, даже если Вы уже знаете, что это — давайте повторим.
Инициализация (от англ. initialize, от initial — «начальный, первоначальный») — это когда мы впервые задаем переменной какое-либо значение.
Например, у нас есть переменная:
Урок №117. Список инициализации членов класса
На этом уроке мы рассмотрим, как инициализировать переменные-члены класса с помощью списка инициализации в языке С++, а также особенности и нюансы, которые при этом могут возникнуть.
Оглавление:
- Списки инициализации членов класса
- uniform-инициализация в C++11
- Инициализация массивов в классе
- Инициализация переменных-членов, которые являются классами
- Использование списков инициализации
- Порядок выполнения в списке инициализации
- Заключение
- Тест
Списки инициализации членов класса
На предыдущем уроке мы инициализировали члены нашего класса в конструкторе через оператор присваивания:
class Values
int m_value1 ;
double m_value2 ;
char m_value3 ;
// Это всё операции присваивания, а не инициализация
m_value1 = 3 ;
m_value2 = 4.5 ;
m_value3 = ‘d’ ;
Сначала создаются m_value1 , m_value2 и m_value3 . Затем выполняется тело конструктора, где этим переменным присваиваются значения. Аналогичен код в не объектно-ориентированном C++:
int m_value1 ;
double m_value2 ;
char m_value3 ;
m_value1 = 3 ;
m_value2 = 4.5 ;
m_value3 = ‘d’ ;
Хотя в плане синтаксиса языка C++ вопросов никаких нет — всё корректно, но более эффективно — использовать инициализацию, а не присваивание после объявления.
Как мы уже знаем из предыдущих уроков, некоторые типы данных (например, константы и ссылки) должны быть инициализированы сразу. Рассмотрим следующий пример:
class Values
const int m_value ;
m_value = 3 ; // ошибка: константам нельзя присваивать значения
Аналогичен код в не объектно-ориентированном C++:
const int m_value ; // ошибка: константы должны быть инициализированы значениями
m_value = 7 ; // ошибка: константам нельзя присваивать значения
Для решения этой проблемы в C++ добавили метод инициализации переменных-членов класса через список инициализации членов, вместо присваивания им значений после объявления. Не путайте этот список с аналогичным списком инициализаторов, который используется для инициализации массивов.
Из урока №28 мы уже знаем, что инициализировать переменные можно тремя способами: через копирующую инициализацию, прямую инициализацию или uniform-инициализацию.
int value1 = 3 ; // копирующая инициализация
double value2 ( 4.5 ) ; // прямая инициализация
char value3 < 'd' >// uniform-инициализация
Использование списка инициализации почти идентично выполнению прямой инициализации (или uniform-инициализации в C++11).
Чтобы было понятнее, рассмотрим пример. Вот код с присваиванием значений переменным-членам класса в конструкторе:
class Values
int m_value1 ;
double m_value2 ;
char m_value3 ;
// Это всё операции присваивания, а не инициализация
m_value1 = 3 ;
m_value2 = 4.5 ;
m_value3 = ‘d’ ;
Теперь давайте перепишем этот код, но уже с использованием списка инициализации:
class Values
int m_value1 ;
double m_value2 ;
char m_value3 ;
Values ( ) : m_value1 ( 3 ) , m_value2 ( 4.5 ) , m_value3 ( ‘d’ ) // напрямую инициализируем переменные-члены класса
// Нет необходимости использовать присваивание
void print ( )
std :: cout << "Values(" << m_value1 << ", " << m_value2 << ", " << m_value3 << ")\n" ;
Values value ;
value . print ( ) ;
Результат выполнения программы:
Список инициализации членов находится сразу же после параметров конструктора. Он начинается с двоеточия ( : ), а затем значение для каждой переменной указывается в круглых скобках. Больше не нужно выполнять операции присваивания в теле конструктора. Также обратите внимание, что список инициализации членов не заканчивается точкой с запятой.
Можно также добавить возможность caller-у передавать значения для инициализации:
class Values
int m_value1 ;
double m_value2 ;
char m_value3 ;
Values ( int value1 , double value2 , char value3 = ‘d’ )
: m_value1 ( value1 ) , m_value2 ( value2 ) , m_value3 ( value3 ) // напрямую инициализируем переменные-члены класса
// Нет необходимости использовать присваивание
void print ( )
std :: cout << "Values(" << m_value1 << ", " << m_value2 << ", " << m_value3 << ")\n" ;
Values value ( 3 , 4.5 ) ; // value1 = 3, value2 = 4.5, value3 = 'd' (значение по умолчанию)
value . print ( ) ;
Результат выполнения программы:
Мы можем использовать параметры по умолчанию для предоставления значений по умолчанию, если пользователь их не предоставил. Например, класс, который имеет константную переменную-член:
class Values
const int m_value ;
Values ( ) : m_value ( 7 ) // напрямую инициализируем константную переменную-член
Это работает, поскольку нам разрешено инициализировать константные переменные (но не присваивать им значения после объявления!).
Правило: Используйте списки инициализации членов, вместо операций присваивания, для инициализации переменных-членов вашего класса.
uniform-инициализация в C++11
В C++11 вместо прямой инициализации можно использовать uniform-инициализацию:
class Values
const int m_value ;
Values ( ) : m_value < 7 >// используем uniform-инициализацию
Настоятельно рекомендуется использовать этот синтаксис (даже если вы не используете константы или ссылки в качестве переменных-членов вашего класса), поскольку списки инициализации членов необходимы при композиции и наследовании (это рассмотрим несколько позже).
Правило: Используйте uniform-инициализацию вместо прямой инициализации в C++11.
Инициализация массивов в классе
Рассмотрим класс с массивом в качестве переменной-члена:
class Values
const int m_array [ 7 ] ;
До C++11 мы могли только обнулить массив через список инициализации:
class Values
const int m_array [ 7 ] ;
Values ( ) : m_array < >// обнуляем массив
// Если мы хотим, чтобы массив имел значения, то мы должны здесь использовать присваивание
Однако в C++11 вы можете полностью инициализировать массив, используя uniform-инициализацию:
class Values
const int m_array [ 7 ] ;
Values ( ) : m_array < 3 , 4 , 5 , 6 , 7 , 8 , 9 >// используем uniform-инициализацию для инициализации массива
Инициализация переменных-членов, которые являются классами
Список инициализации членов также может использоваться для инициализации членов, которые являются классами:
: m_a ( b — 1 ) // вызывается конструктор A(int) для инициализации члена m_a
std :: cout << "B " << b << "\n" ;
Результат выполнения программы:
При создании переменной b вызывается конструктор B(int) со значением 7 . До того, как тело конструктора выполнится, инициализируется m_a , вызывая конструктор A(int) со значением 6 . Таким образом, выведется A 6 . Затем управление возвратится обратно к конструктору B(), и тогда уже он выполнится и выведется B 7 .
Использование списков инициализации
Если список инициализации помещается на той же строке, что и имя конструктора, то лучше всё разместить в одной строке:
class Values
int m_value1 ;
double m_value2 ;
char m_value3 ;
Values ( ) : m_value1 ( 3 ) , m_value2 ( 4.5 ) , m_value3 ( ‘d’ ) // всё находится в одной строке
Если список инициализации членов не помещается в строке с именем конструктора, то на следующей строке (используя перенос) инициализаторы должны быть с отступом:
class Values
int m_value1 ;
double m_value2 ;
char m_value3 ;
Values ( int value1 , double value2 , char value3 = ‘d’ ) // на этой строке уже и так много чего,
: m_value1 ( value1 ) , m_value2 ( value2 ) , m_value3 ( value3 ) // поэтому переносим инициализаторы на новую строку (не забываем использовать отступ)
Если все инициализаторы не помещаются на одной строке, то вы можете выделить для каждого инициализатора отдельную строку:
class Values
int m_value1 ;
double m_value2 ;
char m_value3 ;
float m_value4 ;
Values ( int value1 , double value2 , char value3 = ‘d’ , float value4 = 17.5 ) // на этой строке уже и так много чего,
: m_value1 ( value1 ) , // поэтому выделяем каждому инициализатору отдельную строку, не забывая о запятой в конце
m_value2 ( value2 ) ,
m_value3 ( value3 ) ,
m_value4 ( value4 )
Порядок выполнения в списке инициализации
Удивительно, но переменные в списке инициализации не инициализируются в том порядке, в котором они указаны. Вместо этого они инициализируются в том порядке, в котором объявлены в классе, поэтому следует соблюдать следующие рекомендации:
Не инициализируйте переменные-члены таким образом, чтобы они зависели от других переменных-членов, которые инициализируются первыми (другими словами, убедитесь, что все ваши переменные-члены правильно инициализируются, даже если порядок в списке инициализации отличается).
Инициализируйте переменные в списке инициализации в том порядке, в котором они объявлены в классе.
Заключение
Списки инициализации членов позволяют инициализировать члены, а не присваивать им значения. Это единственный способ инициализации констант и ссылок, которые являются переменными-членами вашего класса. Во многих случаях использование списка инициализации может быть более результативным, чем присваивание значений переменным-членам в теле конструктора. Списки инициализации работают как с переменными фундаментальных типов данных, так и с членами, которые сами являются классами.
Тест
Напишите класс с именем RGBA, который содержит 4 переменные-члены типа std::uint8_t (подключите заголовочный файл cstdint для доступа к типу std::uint8_t):
Присвойте 0 в качестве значения по умолчанию для m_red , m_green и m_blue , и 255 для m_alpha . Создайте конструктор со списком инициализации членов, который позволит пользователю передавать значения для m_red , m_green , m_blue и m_alpha . Напишите функцию print(), которая будет выводить значения переменных-членов.
Подсказка: Если функция print() работает некорректно, то убедитесь, что вы конвертировали std::uint8_t в int.
Следующий код функции main():
Блог

C++ использует списки инициализаторов для инициализации членов данных их значениями.
Списки инициализаторов в основном используются в определенных ситуациях, когда невозможно инициализировать значения в теле конструктора.
Общий синтаксис списка инициализаторов:
| constructorName(type value1, type value2):datamember(value1), datamember(value2) //код конструктора > |
Ситуации, в которых используются списки инициализаторов
1) Конструктор по умолчанию не предоставляется для класса объектов-членов
Когда у нас есть другой объект класса в качестве члена класса, а класс этого объекта не имеет конструктора по умолчанию, мы инициализируем этот объект-член данных, используя список инициализаторов.
Это показано в программе ниже:
using namespace std ;
A :: A ( int val ) <
B :: B ( int val ) : a ( val ) <
cout << "Constructor :: B" ;
| Constructor ::A ; i = 10 Constructor :: B |
В приведенной выше программе объект класса A является членом данных класса B. В классе A у нас есть параметризованный конструктор, но нет конструктора по умолчанию. Таким образом, нам нужно использовать список инициализаторов в классе B, чтобы создать объект класса A.
2) Инициализация элементов данных константы
Поскольку константные элементы данных могут быть инициализированы только один раз, мы инициализируем их с помощью списка инициализаторов.
Читать также: Многомерные массивы в C++
Рассмотрим это в примере ниже:
using namespace std ;
class sample
const int val ;
sample ( int val ) : val ( val )
cout << "Value is " << val ;
sample obj ( 10 ) ;
| Value is 10 |
В приведенной выше программе у нас есть константа в качестве члена данных. Если нам нужно инициализировать этот константный член значением, мы делаем это, используя список инициализаторов, как показано в программе.
3) Инициализация ссылочных типов данных
Ссылки на константы неизменяемы. Поэтому всякий раз, когда нам нужно инициализировать элементы данных ссылочного типа, мы используем списки инициализаторов.
В следующей программе мы инициализируем ссылочный элемент данных, используя список инициализаторов:
using namespace std ;
class sample
sample ( int & ref_val ) : ref_val ( ref_val )
cout << "Value is " << ref_val ;
int ref = 10 ;
sample obj ( ref ) ;
| Value is 10 |
В этой программе у нас есть ссылка в качестве члена данных, и мы инициализируем ее, используя список инициализаторов.
4) Имя элемента и параметр имеют одно и то же имя
Когда имена членов класса должны быть инициализированы параметрами, и эти параметры имеют те же имена, что и имена членов, мы используем список инициализаторов. В наших предыдущих статьях мы использовали указатель this для той же ситуации. Второй вариант — это список инициализаторов.
Читать также: Алгоритмы в STL
Мы изменили приведенный выше пример, с помощью указателя this:
using namespace std ;
class Sample <
Sample ( int num , char ch ) : num ( num ) , ch ( ch ) <
Sample obj ( 100 , 'A' ) ;
| num = 100 ch = A |
В этой программе мы передаем параметры для инициализации элементов данных с одинаковыми именами. В этом случае, мы используем для этой цели список инициализаторов.
5) Повышение производительности
Каждый раз, когда мы инициализируем элементы данных в теле конструктора, для члена данных создается временный объект при присвоении значений. После выполнения назначения этот временный объект удаляется.
Но, если мы используем список инициализаторов, конструктор копирования используется для присвоения значений. Это значительно повышает производительность.
Итог
Таким образом, в этой статье мы рассказали вам о списках инициализаторов, а также о многих ситуациях, в которых нужно их использовать в программировании на C++.
Хотя нам не нужно использовать список инициализаторов, когда таких ситуаций нет, но мы всегда можем использовать их для повышения производительности программы.
В нашей следующей статье мы подробно обсудим дружественные функции в C++, и покажем их работу на примерах.
С Уважением, МониторБанк