Nozioni di base di nullptr
std::nullptr_t
è il tipo del puntatore null letterale, nullptr. È un valore / valore di tipo std::nullptr_t
. Esistono conversioni implicite da nullptr a valore di puntatore null di qualsiasi tipo di puntatore.
Lo 0 letterale è un int, non un puntatore. Se C ++ si trova a guardare 0 in un contesto in cui è possibile utilizzare solo un puntatore, interpreterà a malincuore 0 come puntatore nullo, ma questa è una posizione di fallback. La politica principale di C ++ è che 0 è un int, non un puntatore.
Vantaggio 1: rimuovere l'ambiguità in caso di sovraccarico su puntatore e tipi integrali
In C ++ 98, la principale conseguenza di ciò era che il sovraccarico su puntatori e tipi integrali poteva portare a sorprese. Passare 0 o NULL a tali sovraccarichi non ha mai chiamato un sovraccarico del puntatore:
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*)
La cosa interessante di quella chiamata è la contraddizione tra il significato apparente del codice sorgente ("Sto chiamando divertimento con NULL-il puntatore null") e il suo significato reale ("Sto chiamando divertimento con un qualche tipo di numero intero - non il null puntatore”).
Il vantaggio di nullptr è che non ha un tipo integrale. Chiamare la funzione sovraccarico divertente con nullptr chiama il void * overload (ovvero il sovraccarico del puntatore), poiché nullptr non può essere visto come qualcosa di integrale:
fun(nullptr); // calls fun(void*) overload
L'uso di nullptr anziché 0 o NULL evita quindi sorprese nella risoluzione del sovraccarico.
Un altro vantaggio di nullptr
over NULL(0)
quando si utilizza auto per il tipo restituito
Ad esempio, supponiamo di riscontrarlo in una base di codice:
auto result = findRecord( /* arguments */ );
if (result == 0) {
....
}
Se non ti capita di sapere (o non riesci a scoprire facilmente) cosa restituisce findRecord, potrebbe non essere chiaro se il risultato sia un tipo di puntatore o un tipo integrale. Dopotutto, 0 (con quale risultato viene testato) potrebbe andare in entrambi i modi. Se vedi quanto segue, d'altra parte,
auto result = findRecord( /* arguments */ );
if (result == nullptr) {
...
}
non c'è ambiguità: il risultato deve essere un tipo di puntatore.
Vantaggio 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;
}
Il programma sopra compilato ed eseguito correttamente ma lockAndCallF1, lockAndCallF2 e lockAndCallF3 hanno un codice ridondante. È un peccato scrivere codice in questo modo se possiamo scrivere un modello per tutti questi lockAndCallF1, lockAndCallF2 & lockAndCallF3
. Quindi può essere generalizzato con template. Ho scritto la funzione modello lockAndCall
anziché la definizione multipla lockAndCallF1, lockAndCallF2 & lockAndCallF3
per il codice ridondante.
Il codice viene ricodificato come di seguito:
#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;
}
Analisi di dettaglio per cui la compilazione non è riuscita per lockAndCall(f1, f1m, 0) & lockAndCall(f3, f3m, nullptr)
non perlockAndCall(f3, f3m, nullptr)
Perché compilazione lockAndCall(f1, f1m, 0) & lockAndCall(f3, f3m, nullptr)
fallita?
Il problema è che quando 0 viene passato a lockAndCall, inizia la deduzione del tipo di modello per capirne il tipo. Il tipo di 0 è int, quindi questo è il tipo di parametro ptr all'interno dell'istanza di questa chiamata a lockAndCall. Sfortunatamente, questo significa che nella chiamata a func all'interno di lockAndCall, viene passato un int, che non è compatibile con il std::shared_ptr<int>
parametro che si f1
aspetta. Lo 0 passato nella chiamata a lockAndCall
doveva rappresentare un puntatore nullo, ma ciò che effettivamente veniva passato era int. Cercare di passare questo int a f1 come a std::shared_ptr<int>
è un errore di tipo. La chiamata a lockAndCall
con 0 non riesce perché all'interno del modello, un int viene passato a una funzione che richiede un std::shared_ptr<int>
.
L'analisi per la chiamata che coinvolge NULL
è essenzialmente la stessa. Quando NULL
viene passato lockAndCall
, viene dedotto un tipo integrale per il parametro ptr e si verifica un errore di tipo quando si passa a un tipo ptr
int o simile a int f2
che si aspetta di ottenere un std::unique_ptr<int>
.
Al contrario, la chiamata in questione nullptr
non ha problemi. Quando nullptr
viene passato lockAndCall
, ptr
viene dedotto il tipo per std::nullptr_t
. Quando ptr
viene passato a f3
, c'è una conversione implicita da std::nullptr_t
a int*
, perché si std::nullptr_t
converte implicitamente in tutti i tipi di puntatore.
Si consiglia, ogni volta che si desidera fare riferimento a un puntatore null, utilizzare nullptr, non 0 o NULL
.
int
evoid *
non sceglierà laint
versione rispetto allavoid *
versione durante l'utilizzonullptr
.