Conceptos básicos de nullptr
std::nullptr_tes el tipo del puntero nulo literal, nullptr. Es un valor / valor de tipo std::nullptr_t. Existen conversiones implícitas de nullptr a valor de puntero nulo de cualquier tipo de puntero.
El 0 literal es un int, no un puntero. Si C ++ se encuentra mirando a 0 en un contexto donde solo se puede usar un puntero, interpretará a regañadientes 0 como un puntero nulo, pero esa es una posición alternativa. La política principal de C ++ es que 0 es un int, no un puntero.
Ventaja 1: elimine la ambigüedad al sobrecargar el puntero y los tipos integrales
En C ++ 98, la implicación principal de esto fue que la sobrecarga en punteros y tipos integrales podría generar sorpresas. Pasar 0 o NULL a tales sobrecargas nunca se llamó sobrecarga de puntero:
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*)
Lo interesante de esa llamada es la contradicción entre el significado aparente del código fuente ("Estoy llamando divertido con NULL-el puntero nulo") y su significado real ("Estoy llamando divertido con algún tipo de entero, no el nulo puntero").
La ventaja de nullptr es que no tiene un tipo integral. Llamar divertido a la función sobrecargada con nullptr llama a la sobrecarga void * (es decir, la sobrecarga del puntero), porque nullptr no puede verse como algo integral:
fun(nullptr); // calls fun(void*) overload
Usar nullptr en lugar de 0 o NULL evita las sorpresas de resolución de sobrecarga.
Otra ventaja de nullptrsobre NULL(0)cuando se usa auto para el tipo de retorno
Por ejemplo, suponga que encuentra esto en una base de código:
auto result = findRecord( /* arguments */ );
if (result == 0) {
....
}
Si no sabe (o no puede descubrir fácilmente) qué devuelve findRecord, puede que no esté claro si el resultado es un tipo de puntero o un tipo integral. Después de todo, 0 (con qué resultado se prueba) podría ir en cualquier dirección. Si ve lo siguiente, por otro lado,
auto result = findRecord( /* arguments */ );
if (result == nullptr) {
...
}
no hay ambigüedad: el resultado debe ser un tipo de puntero.
Ventaja 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;
}
El programa anterior se compila y ejecuta con éxito, pero lockAndCallF1, lockAndCallF2 y lockAndCallF3 tienen código redundante. Es una pena escribir código como este si podemos escribir una plantilla para todo esto lockAndCallF1, lockAndCallF2 & lockAndCallF3. Por lo tanto, se puede generalizar con plantilla. He escrito la función de plantilla en lockAndCalllugar de la definición múltiple lockAndCallF1, lockAndCallF2 & lockAndCallF3para código redundante.
El código se refactoriza de la siguiente manera:
#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;
}
Análisis detallado de por qué la compilación falló para lockAndCall(f1, f1m, 0) & lockAndCall(f3, f3m, nullptr)no paralockAndCall(f3, f3m, nullptr)
¿Por qué la compilación de lockAndCall(f1, f1m, 0) & lockAndCall(f3, f3m, nullptr)fallido?
El problema es que cuando se pasa 0 a lockAndCall, la deducción de tipo de plantilla entra en acción para descubrir su tipo. El tipo de 0 es int, entonces ese es el tipo del parámetro ptr dentro de la instanciación de esta llamada a lockAndCall. Desafortunadamente, esto significa que en la llamada a func dentro de lockAndCall, se pasa un int, y eso no es compatible con el std::shared_ptr<int>parámetro que f1espera. El 0 pasado en la llamada a lockAndCallestaba destinado a representar un puntero nulo, pero lo que realmente pasó fue int. Intentar pasar este int a f1 como a std::shared_ptr<int>es un error de tipo. La llamada a lockAndCallcon 0 falla porque dentro de la plantilla, se pasa un int a una función que requiere a std::shared_ptr<int>.
El análisis para la llamada que involucra NULLes esencialmente el mismo. Cuando NULLse pasa a lockAndCall, se deduce un tipo integral para el parámetro ptr, y se produce un error de tipo cuando se pasa a un tipo ptrint o tipo int f2, que espera obtener a std::unique_ptr<int>.
En contraste, la llamada que involucra nullptrno tiene problemas. Cuando nullptrse pasa a lockAndCall, el tipo para ptrse deduce que es std::nullptr_t. Cuando ptrse pasa a f3, hay una conversión implícita de std::nullptr_ta int*, porque se std::nullptr_tconvierte implícitamente a todos los tipos de puntero.
Se recomienda, siempre que desee hacer referencia a un puntero nulo, use nullptr, no 0 o NULL.
intyvoid *no elegirá laintversión sobre lavoid *versión cuando se usanullptr.