Grundlagen von nullptr
std::nullptr_t
ist der Typ des Nullzeigerliterals nullptr. Es ist ein Wert vom Typ prvalue / rvalue std::nullptr_t
. Es gibt implizite Konvertierungen von nullptr in null Zeigerwerte für jeden Zeigertyp.
Das Literal 0 ist ein int, kein Zeiger. Wenn C ++ 0 in einem Kontext betrachtet, in dem nur ein Zeiger verwendet werden kann, interpretiert es 0 widerwillig als Nullzeiger, aber das ist eine Fallback-Position. Die primäre Richtlinie von C ++ lautet, dass 0 ein int und kein Zeiger ist.
Vorteil 1 - Entfernen Sie Mehrdeutigkeiten beim Überladen von Zeiger- und Integraltypen
In C ++ 98 war die Hauptaussage, dass eine Überladung von Zeiger- und Integraltypen zu Überraschungen führen kann. Das Übergeben von 0 oder NULL an solche Überladungen wird niemals als Zeigerüberladung bezeichnet:
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*)
Das Interessante an diesem Aufruf ist der Widerspruch zwischen der offensichtlichen Bedeutung des Quellcodes („Ich rufe Spaß mit NULL - dem Nullzeiger“) und seiner tatsächlichen Bedeutung („Ich rufe Spaß mit einer Art Ganzzahl auf - nicht der Null Zeiger").
Der Vorteil von nullptr ist, dass es keinen integralen Typ hat. Das Aufrufen der überladenen Funktion fun mit nullptr ruft die void * -Überladung (dh die Zeigerüberladung) auf, da nullptr nicht als etwas Integrales angesehen werden kann:
fun(nullptr); // calls fun(void*) overload
Die Verwendung von nullptr anstelle von 0 oder NULL vermeidet somit Überraschungen bei der Überlastungsauflösung.
Ein weiterer Vorteil nullptr
gegenüber der NULL(0)
Verwendung von Auto für den Rückgabetyp
Angenommen, Sie stoßen in einer Codebasis auf Folgendes:
auto result = findRecord( /* arguments */ );
if (result == 0) {
....
}
Wenn Sie nicht wissen (oder nicht leicht herausfinden können), was findRecord zurückgibt, ist möglicherweise nicht klar, ob das Ergebnis ein Zeigertyp oder ein ganzzahliger Typ ist. Immerhin könnte 0 (gegen welches Ergebnis getestet wird) in beide Richtungen gehen. Wenn Sie andererseits Folgendes sehen,
auto result = findRecord( /* arguments */ );
if (result == nullptr) {
...
}
Es gibt keine Mehrdeutigkeit: Das Ergebnis muss ein Zeigertyp sein.
Vorteil 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;
}
Das obige Programm wurde kompiliert und erfolgreich ausgeführt, aber lockAndCallF1, lockAndCallF2 und lockAndCallF3 haben redundanten Code. Es ist schade, solchen Code zu schreiben, wenn wir für all dies eine Vorlage schreiben können lockAndCallF1, lockAndCallF2 & lockAndCallF3
. So kann es mit Vorlage verallgemeinert werden. Ich habe eine Vorlagenfunktion lockAndCall
anstelle einer Mehrfachdefinition lockAndCallF1, lockAndCallF2 & lockAndCallF3
für redundanten Code geschrieben.
Der Code wird wie folgt umgerechnet:
#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;
}
Detail - Analyse , warum Kompilation für gescheiterte lockAndCall(f1, f1m, 0) & lockAndCall(f3, f3m, nullptr)
für nichtlockAndCall(f3, f3m, nullptr)
Warum Kompilierung lockAndCall(f1, f1m, 0) & lockAndCall(f3, f3m, nullptr)
fehlgeschlagen?
Das Problem ist, dass bei der Übergabe von 0 an lockAndCall der Abzug des Vorlagentyps aktiviert wird, um den Typ herauszufinden. Der Typ 0 ist int, das ist also der Typ des Parameters ptr innerhalb der Instanziierung dieses Aufrufs von lockAndCall. Leider bedeutet dies, dass beim Aufruf von func in lockAndCall ein int übergeben wird, das nicht mit dem erwarteten std::shared_ptr<int>
Parameter kompatibel ist f1
. Die im Aufruf an übergebene 0 lockAndCall
sollte einen Nullzeiger darstellen, aber was tatsächlich übergeben wurde, war int. Der Versuch, dieses int als a an f1 zu übergeben, std::shared_ptr<int>
ist ein Typfehler. Der Aufruf von lockAndCall
with 0 schlägt fehl, da innerhalb der Vorlage ein int an eine Funktion übergeben wird, für die a erforderlich ist std::shared_ptr<int>
.
Die Analyse für den Anruf NULL
ist im Wesentlichen dieselbe. Bei der NULL
Übergabe an lockAndCall
wird ein integraler Typ für den Parameter ptr abgeleitet, und ein Typfehler tritt auf, wenn ptr
- ein int- oder int-ähnlicher Typ - an übergeben wird f2
, der erwartet, dass a erhalten wird std::unique_ptr<int>
.
Im Gegensatz dazu hat der Anruf nullptr
keine Probleme. Wenn an übergeben nullptr
wird, lockAndCall
wird der Typ für ptr
als abgeleitet std::nullptr_t
. Wenn an übergeben ptr
wird, f3
erfolgt eine implizite Konvertierung von std::nullptr_t
nach int*
, da std::nullptr_t
implizit in alle Zeigertypen konvertiert wird.
Es wird empfohlen, wann immer Sie auf einen Nullzeiger verweisen möchten, nullptr zu verwenden, nicht 0 oder NULL
.
int
undvoid *
wählt bei der Verwendung nicht dieint
Version aus .void *
nullptr