Conceptos básicos de nullptr
std::nullptr_t
es 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 nullptr
sobre 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 lockAndCall
lugar de la definición múltiple lockAndCallF1, lockAndCallF2 & lockAndCallF3
para 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 f1
espera. El 0 pasado en la llamada a lockAndCall
estaba 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 lockAndCall
con 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 NULL
es esencialmente el mismo. Cuando NULL
se 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 ptr
int o tipo int f2
, que espera obtener a std::unique_ptr<int>
.
En contraste, la llamada que involucra nullptr
no tiene problemas. Cuando nullptr
se pasa a lockAndCall
, el tipo para ptr
se deduce que es std::nullptr_t
. Cuando ptr
se pasa a f3
, hay una conversión implícita de std::nullptr_t
a int*
, porque se std::nullptr_t
convierte 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
.
int
yvoid *
no elegirá laint
versión sobre lavoid *
versión cuando se usanullptr
.