Читать «Эффективный и современный С++. 42 рекомендации по использованию С++11 и С++14» онлайн - страница 43

Скотт Мейерс

Это приводит нас к завершению изучения фигурных инициализаторов и перегрузки конструкторов, но есть еще один интересный предельный случай, который хотелось бы рассмотреть. Предположим, что вы используете пустые фигурные скобки для создания объекта, который поддерживает конструктор по умолчанию и конструктор с std::initializer_list. Что при этом будут означать пустые фигурные скобки? Если они означают “без аргументов”, будет вызван конструктор по умолчанию, но если они означают “пустой std::initializer_list”, то будет вызван конструктор с std::initializer_list без элементов.

Правило заключается в том, что будет вызван конструктор по умолчанию. Пустые фигурные скобки означают отсутствие аргументов, а не пустой std::initializer_list:

class Widget {

public:

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

 Widget();

 // Конструктор с std::initializer_list

 Widget(std::initializer_list<int> il);

 // Нет функций неявного преобразования

};

Widget w1;   // Вызов конструктора по умолчанию

Widget w2{}; // Вызов конструктора по умолчанию

Widget w3(); // Трактуется как объявление функции!

Если вы хотите вызвать конструктор с пустым std::initializer_list, то это можно сделать, передавая пустые фигурные скобки в качестве аргумента конструктора в круглых или фигурных скобках, окружающих передаваемые вами:

Widget w4({}); // Вызов конструктора с пустым

               // std::initializer_list

Widget w5{{}}; // То же самое

Сейчас, когда кажущиеся магическими правила фигурной инициализации, std::initializer_list и перегрузки конструкторов переполняют ваш мозг, вы можете удивиться, какое большое количество информации влияет на повседневное программирование. На самом деле даже больше, чем вы думаете, потому что одним из классов, на которые все это оказывает непосредственное влияние, является std::vector. Класс std::vector имеет конструктор без std::initializer_list, который позволяет вам указать начальный размер контейнера и значение, присваиваемое каждому из его элементов; но при этом имеется также конструктор, принимающий std::initializer_list и позволяющий указать начальные значения контейнера. Если вы создаете std::vector числового типа (например, std::vector<int>) и передаете ему два аргумента, то при использовании круглых и фигурных скобок вы получите совершенно разные результаты:

std::vector<int> v1(10, 20); // Используется конструктор без

                             // std::initializer_list: создает

                             // std::vector с 10 элементами;

                             // значение каждого равно 20

std::vector<int> v2{10, 20}; // Используется конструктор с

                             // std::initializer_list: создает

                             // std::vector с 2 элементами со

                             // значениями 10 и 20

Но давайте сделаем шаг назад от std::vector, а также от деталей применения круглых скобок, фигурных скобок и правил перегрузки конструкторов. Имеется два основных вывода из этого обсуждения. Во-первых, как автор класса вы должны быть осведомлены о том, что если ваш набор перегружаемых конструкторов включает один или несколько конструкторов, использующих std::initializer_list, то клиентский код с фигурной инициализацией может рассматривать только перегрузки с std::initializer_list. В результате лучше проектировать конструкторы так, чтобы перегрузка не зависела от того, используете вы круглые или фигурные скобки. Другими словами, вынесите уроки из того, что сейчас рассматривается как ошибка дизайна интерфейса класса std::vector, и проектируйте свои классы так, чтобы избегать подобных ошибок.