Читать «Эффективный и современный С++. 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
таким перегрузкам никогда не приводила к вызову функции с указателем: