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

Скотт Мейерс

template<typename T,     // Тип создаваемого объекта

         typename... Ts> // Типы используемых аргументов

void doSomeWork(Ts&&... params) {

 Создание локального объекта T из params...

}

Есть два способа превратить строку псевдокода в реальный код (см. в разделе 5.3 информацию о std::forward):

T localObject(std::forward<Ts>(params)...); // Круглые скобки

T localObject{std::forward<Ts>(params)...}; // Фигурные скобки

Рассмотрим следующий вызывающий код:

std::vector<int> v;

doSomeWork<std::vector<int>>(10, 20);

Если doSomeWork использует при создании объекта localObject круглые скобки, в результате будет получен std::vector с 10 элементами. Если же doSomeWork использует фигурные скобки, то результатом будет std::vector с двумя элементами. Какой из этих вариантов корректен! Автор doSomeWork не может этого знать. Это может знать только вызывающий код.

Это именно та проблема, которая встает перед функциями стандартной библиотеки std::make_unique и std::make_shared (см. раздел 4.4). Эти функции решают проблему, используя круглые скобки и документируя это решение как части своих интерфейсов.

Следует запомнить

• Фигурная инициализация является наиболее широко используемым синтаксисом инициализации, предотвращающим сужающие преобразования и нечувствительным к особенностям синтаксического анализа С++.

• В процессе разрешения перегрузки конструкторов фигурные инициализаторы соответствуют параметрам std::initializer_list, если это возможно, даже если другие конструкторы обеспечивают лучшее соответствие.

• Примером, в котором выбор между круглыми и фигурными скобками приводит к значительно отличающимся результатам, является создание std::vector<числовой_тип> с двумя аргументами.

• Выбор между круглыми и фигурными скобками для создания объектов внутри шаблонов может быть очень сложным.

3.2. Предпочитайте nullptr значениям 0 и NULL

Дело вот в чем: литерал 0 представляет собой int, а не указатель. Если С++ встретит 0 в контексте, где может использоваться только указатель, он интерпретирует 0 как нулевой указатель, но это — запасной выход. Фундаментальная стратегия С++ состоит в том, что 0 — это значение типа int, а не указатель.

С практической точки зрения то же самое относится и к NULL. В случае NULL имеется некоторая неопределенность в деталях, поскольку реализациям позволено придавать NULL целочисленный тип, отличный от int (например, long). Это не является распространенной практикой, но в действительности не имеет значения, поскольку вопрос не в точном типе NULL, а в том, что ни 0, ни NULL не имеют тип указателя.

В С++98 основным следствием этого факта было то, что перегрузка с использованием типов указателей и целочисленных типов могла вести к сюрпризам. Передача 0 или NULL таким перегрузкам никогда не приводила к вызову функции с указателем: