Читать «Эффективный и современный С++. 42 рекомендации по использованию С++11 и С++14» онлайн - страница 46
Скотт Мейерс
void f(int); // Три перегрузки функции f
void f(bool);
void f(void*);
f(0); // Вызов f(int), не f(void*)
f(NULL); // Может не компилироваться, но обычно
// вызывает f(int) и никогда - f(void*)
Неопределенность в отношении поведения f(NULL)
является отражением свободы предоставленной реализациям в отношении типа NULL
. Если NULL
определен, например, как 0L
(т.e. 0
как значение типа long
), то вызов является неоднозначным, поскольку преобразования long
в int
, long
в bool
и 0L
в void*
рассматриваются как одинаково подходящие. Интересно, что этот вызов является противоречием между f
с нулевым указателем NULL
”) и 0
и NULL
, несмотря на то что nullptr
является лучшим выбором.
Преимущество nullptr
заключается в том, что это значение не является значением целочисленного типа. Честно говоря, он не имеет и типа указателя, но его можно рассматривать как указатель nullptr
является std::nullptr_t
, ну, а тип std::nullptr_t
циклически определяется как тип значения nullptr
… Тип std::nullptr_t
неявно преобразуется во все типы обычных указателей, и именно это делает nullptr
действующим как указатель всех типов.
Вызов перегруженной функции f
с nullptr
приводит к вызову перегрузки void*
(т.e. перегрузки с указателем), поскольку nullptr
нельзя рассматривать как что-то целочисленное:
f(nullptr); // Вызов f(void*)
Использование nullptr
вместо 0
или NULL
, таким образом, позволяет избежать сюрпризов перегрузки, но это не единственное его преимущество. Оно позволяет также повысить ясность кода, в особенности при применении auto
-переменных. Предположим, например, что у нас есть следующий исходный текст:
auto result = findRecord( /* Аргументы */ );
if (result == 0) {
…
}
Если вы случайно не знаете (или не можете быстро найти), какой тип возвращает findRecord
, может быть неясно, имеет ли result
тип указателя или целочисленный тип. В конце концов, значение 0
(с которым сравнивается result
) может быть в обоих случаях. С другой стороны, если вы увидите код
auto result = findRecord( /* Аргументы */ );
if (result == nullptr) {
…
}
то здесь нет никакой неоднозначности: result должен иметь тип указателя.
Особенно ярко сияет nullptr
, когда на сцене появляются шаблоны. Предположим, что у вас есть несколько функций, которые должны вызываться только при блокировке соответствующего мьютекса. Каждая функция получает указатель определенного вида:
int f1(std::shared_ptr<Widget> spw); // Вызывается только при
double f2(std::unique_ptr<Widget> upw); // блокировке соответ-
bool f3(Widget* pw); // ствующего мьютекса