Основы nullptr
std::nullptr_t
является типом литерала нулевого указателя, nullptr. Это prvalue / rvalue типа std::nullptr_t
. Существуют неявные преобразования из nullptr в нулевое значение указателя любого типа указателя.
Литерал 0 - это целое число, а не указатель. Если C ++ обнаруживает, что смотрит на 0 в контексте, где может использоваться только указатель, он неохотно интерпретирует 0 как нулевой указатель, но это запасная позиция. Основная политика C ++ заключается в том, что 0 - это int, а не указатель.
Преимущество 1 - устранение неоднозначности при перегрузке указателя и целочисленных типов
В C ++ 98 основной причиной этого было то, что перегрузка указателей и целочисленных типов может привести к неожиданностям. Передача 0 или NULL таким перегрузкам никогда не вызывает перегрузку указателя:
void fun(int); // two overloads of fun
void fun(void*);
fun(0); // calls f(int), not fun(void*)
fun(NULL); // might not compile, but typically calls fun(int). Never calls fun(void*)
Интересным в этом вызове является противоречие между кажущимся значением исходного кода («я называю fun с помощью NULL-нулевого указателя») и его фактическим значением («я вызываю fun с каким-то целым числом, а не нулевым»). указатель").
Преимущество nullptr в том, что он не имеет целочисленного типа. Вызов перегруженной функции fun с nullptr вызывает перегрузку void * (т. Е. Перегрузку указателя), потому что nullptr не может рассматриваться как что-то целое:
fun(nullptr); // calls fun(void*) overload
Использование nullptr вместо 0 или NULL позволяет избежать неожиданностей при перегрузке.
Еще одно преимущество по nullptr
сравнению NULL(0)
с использованием авто для типа возврата
Например, предположим, что вы столкнулись с этим в кодовой базе:
auto result = findRecord( /* arguments */ );
if (result == 0) {
....
}
Если вы случайно не знаете (или не можете легко выяснить), что возвращает findRecord, может быть неясно, является ли тип результата указателем или целым типом. В конце концов, 0 (какой результат проверяется) может пойти в любом случае. Если вы видите следующее, с другой стороны,
auto result = findRecord( /* arguments */ );
if (result == nullptr) {
...
}
нет никакой двусмысленности: результат должен быть указателем типа.
Преимущество 3
#include<iostream>
#include <memory>
#include <thread>
#include <mutex>
using namespace std;
int f1(std::shared_ptr<int> spw) // call these only when
{
//do something
return 0;
}
double f2(std::unique_ptr<int> upw) // the appropriate
{
//do something
return 0.0;
}
bool f3(int* pw) // mutex is locked
{
return 0;
}
std::mutex f1m, f2m, f3m; // mutexes for f1, f2, and f3
using MuxtexGuard = std::lock_guard<std::mutex>;
void lockAndCallF1()
{
MuxtexGuard g(f1m); // lock mutex for f1
auto result = f1(static_cast<int>(0)); // pass 0 as null ptr to f1
cout<< result<<endl;
}
void lockAndCallF2()
{
MuxtexGuard g(f2m); // lock mutex for f2
auto result = f2(static_cast<int>(NULL)); // pass NULL as null ptr to f2
cout<< result<<endl;
}
void lockAndCallF3()
{
MuxtexGuard g(f3m); // lock mutex for f2
auto result = f3(nullptr);// pass nullptr as null ptr to f3
cout<< result<<endl;
} // unlock mutex
int main()
{
lockAndCallF1();
lockAndCallF2();
lockAndCallF3();
return 0;
}
Выше программа компилируется и выполняется успешно, но lockAndCallF1, lockAndCallF2 и lockAndCallF3 имеют избыточный код. Жаль писать такой код, если мы можем написать шаблон для всех этих lockAndCallF1, lockAndCallF2 & lockAndCallF3
. Так что это можно обобщить с помощью шаблона. Я написал функцию шаблона lockAndCall
вместо множественного определения lockAndCallF1, lockAndCallF2 & lockAndCallF3
для избыточного кода.
Код пересчитан, как показано ниже:
#include<iostream>
#include <memory>
#include <thread>
#include <mutex>
using namespace std;
int f1(std::shared_ptr<int> spw) // call these only when
{
//do something
return 0;
}
double f2(std::unique_ptr<int> upw) // the appropriate
{
//do something
return 0.0;
}
bool f3(int* pw) // mutex is locked
{
return 0;
}
std::mutex f1m, f2m, f3m; // mutexes for f1, f2, and f3
using MuxtexGuard = std::lock_guard<std::mutex>;
template<typename FuncType, typename MuxType, typename PtrType>
auto lockAndCall(FuncType func, MuxType& mutex, PtrType ptr) -> decltype(func(ptr))
//decltype(auto) lockAndCall(FuncType func, MuxType& mutex, PtrType ptr)
{
MuxtexGuard g(mutex);
return func(ptr);
}
int main()
{
auto result1 = lockAndCall(f1, f1m, 0); //compilation failed
//do something
auto result2 = lockAndCall(f2, f2m, NULL); //compilation failed
//do something
auto result3 = lockAndCall(f3, f3m, nullptr);
//do something
return 0;
}
Подробный анализ , почему компиляция не удалось по lockAndCall(f1, f1m, 0) & lockAndCall(f3, f3m, nullptr)
неlockAndCall(f3, f3m, nullptr)
Почему сборка lockAndCall(f1, f1m, 0) & lockAndCall(f3, f3m, nullptr)
не удалась?
Проблема в том, что когда 0 передается в lockAndCall, вычитается тип шаблона, чтобы выяснить его тип. Тип 0 - это int, так что это тип параметра ptr внутри экземпляра этого вызова lockAndCall. К сожалению, это означает, что при вызове func внутри lockAndCall передается int, что несовместимо с ожидаемым std::shared_ptr<int>
параметром f1
. 0, переданный в вызове, lockAndCall
предназначался для представления нулевого указателя, но в действительности было передано int. Попытка передать это int в f1 как std::shared_ptr<int>
ошибка типа. Вызов lockAndCall
с 0 завершается неудачно, потому что внутри шаблона int передается функции, которая требует std::shared_ptr<int>
.
Анализ для вызова, NULL
по сути, такой же. Когда NULL
передается lockAndCall
, целочисленный тип выводится для параметра ptr, и ошибка типа возникает, когда ptr
передается - тип int или int-like f2
, который ожидает получить a std::unique_ptr<int>
.
В отличие от вызова с участием nullptr
не имеет проблем. Когда nullptr
передается lockAndCall
, тип для ptr
выводится как std::nullptr_t
. Когда ptr
передается f3
, есть неявное преобразование из std::nullptr_t
в int*
, потому что std::nullptr_t
неявно преобразуется во все типы указателей.
Рекомендуется, если вы хотите сослаться на пустой указатель, используйте nullptr, а не 0 или NULL
.
int
иvoid *
не будет выбиратьint
версию надvoid *
версией при использованииnullptr
.