Noções básicas de nullptr
std::nullptr_t
é o tipo do literal de ponteiro nulo, nullptr. É um prvalue / rvalue do tipo std::nullptr_t
. Existem conversões implícitas de nullptr para valor de ponteiro nulo de qualquer tipo de ponteiro.
O literal 0 é um int, não um ponteiro. Se o C ++ se encontrar olhando para 0 em um contexto onde apenas um ponteiro pode ser usado, interpretará de má vontade 0 como um ponteiro nulo, mas essa é uma posição de fallback. A política principal do C ++ é que 0 é um int, não um ponteiro.
Vantagem 1 - Remova a ambiguidade ao sobrecarregar nos tipos ponteiro e integral
No C ++ 98, a principal implicação disso era que a sobrecarga nos tipos ponteiro e integral poderia levar a surpresas. Passar 0 ou NULL a essas sobrecargas nunca chamou sobrecarga de ponteiro:
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*)
O interessante dessa chamada é a contradição entre o significado aparente do código-fonte ("Estou chamando diversão com NULL - o ponteiro nulo") e seu significado real ("Estou chamando diversão com algum tipo de número inteiro - não o nulo ponteiro ").
A vantagem do nullptr é que ele não possui um tipo integral. A diversão da função sobrecarregada com nullptr chama a sobrecarga void * (ou seja, a sobrecarga do ponteiro), porque nullptr não pode ser visto como algo integral:
fun(nullptr); // calls fun(void*) overload
Usar nullptr em vez de 0 ou NULL evita surpresas na resolução de sobrecarga.
Outra vantagem de nullptr
mais NULL(0)
quando se usa auto para o tipo de retorno
Por exemplo, suponha que você encontre isso em uma base de código:
auto result = findRecord( /* arguments */ );
if (result == 0) {
....
}
Se você não souber (ou não conseguir descobrir facilmente) o que findRecord retorna, pode não estar claro se o resultado é um tipo de ponteiro ou um tipo integral. Afinal, 0 (cujo resultado é testado) poderia ser de qualquer maneira. Se você vir o seguinte, por outro lado,
auto result = findRecord( /* arguments */ );
if (result == nullptr) {
...
}
não há ambiguidade: o resultado deve ser do tipo ponteiro.
Vantagem 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;
}
O programa acima é compilado e executado com êxito, mas lockAndCallF1, lockAndCallF2 e lockAndCallF3 têm código redundante. É uma pena escrever um código como este se pudermos escrever um modelo para tudo isso lockAndCallF1, lockAndCallF2 & lockAndCallF3
. Portanto, pode ser generalizado com o modelo. Eu escrevi a função de modelo em lockAndCall
vez de várias definições lockAndCallF1, lockAndCallF2 & lockAndCallF3
para código redundante.
O código é re-fatorado como abaixo:
#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álise detalhada por que a compilação falhou, lockAndCall(f1, f1m, 0) & lockAndCall(f3, f3m, nullptr)
não paralockAndCall(f3, f3m, nullptr)
Por que a compilação de lockAndCall(f1, f1m, 0) & lockAndCall(f3, f3m, nullptr)
falhou?
O problema é que, quando 0 é passado para lockAndCall, a dedução do tipo de modelo entra em ação para descobrir seu tipo. O tipo 0 é int, portanto esse é o tipo do parâmetro ptr dentro da instanciação dessa chamada para lockAndCall. Infelizmente, isso significa que, na chamada para funcionar dentro de lockAndCall, um int está sendo passado e isso não é compatível com o std::shared_ptr<int>
parâmetro f1
esperado. O 0 passado na chamada para lockAndCall
era destinado a representar um ponteiro nulo, mas o que realmente foi passado foi int. Tentar passar essa int para f1 como a std::shared_ptr<int>
é um erro de tipo. A chamada para lockAndCall
com 0 falha porque, dentro do modelo, um int está sendo passado para uma função que requer a std::shared_ptr<int>
.
A análise da chamada envolvendo NULL
é essencialmente a mesma. Quando NULL
é passado para lockAndCall
, um tipo integral é deduzido para o parâmetro ptr, e ocorre um erro de tipo quando ptr
- um tipo int ou int-like - é passado para f2
, o qual espera obter a std::unique_ptr<int>
.
Por outro lado, a ligação envolvida nullptr
não apresenta problemas. Quando nullptr
é passado para lockAndCall
, o tipo de ptr
é deduzido como sendo std::nullptr_t
. Quando ptr
é passado para f3
, há uma conversão implícita de std::nullptr_t
para int*
, porque std::nullptr_t
converte implicitamente em todos os tipos de ponteiro.
É recomendável, sempre que você desejar se referir a um ponteiro nulo, use nullptr, não 0 ou NULL
.
int
evoid *
não escolhe aint
versão sobre avoid *
versão ao usarnullptr
.