Dieser Code macht konzeptionell dasselbe für die drei Zeiger (sichere Zeigerinitialisierung):
int* p1 = nullptr;
int* p2 = NULL;
int* p3 = 0;
Was sind also die Vorteile der Zuweisung von Zeigern nullptr
gegenüber der Zuweisung von Werten NULL
oder 0
?
int
undvoid *
wählt bei der Verwendung nicht dieint
Version aus .void *
nullptr
f(nullptr)
ist anders alsf(NULL)
. In Bezug auf den obigen Code (Zuweisung zu einer lokalen Variablen) sind jedoch alle drei Zeiger genau gleich. Der einzige Vorteil ist die Lesbarkeit des Codes.nullptr
den Unterschied zwischen 0 undNULL
Antworten:
In diesem Code scheint es keinen Vorteil zu geben. Beachten Sie jedoch die folgenden überladenen Funktionen:
Welche Funktion wird aufgerufen? Natürlich soll hier angerufen werden
f(char const *)
, aber in Wirklichkeitf(int)
wird angerufen! Das ist ein großes Problem 1 , nicht wahr?Die Lösung für solche Probleme besteht also darin, Folgendes zu verwenden
nullptr
:Das ist natürlich nicht der einzige Vorteil von
nullptr
. Hier ist noch einer:Da in der Vorlage der Typ von
nullptr
abgeleitet wirdnullptr_t
, können Sie Folgendes schreiben:1. In C ++
NULL
ist definiert als#define NULL 0
, so ist es im Grundeint
, deshalbf(int)
wird aufgerufen.quelle
nullptr
? (Nein, ich fordere nicht)NULL
wird vom Standard verlangt, einen integralen Typ zu haben, und deshalb wird er normalerweise als0
oder definiert0L
. Ich bin mir auch nicht sicher, ob mir diesenullptr_t
Überladung gefällt , da sie nur Aufrufe mitnullptr
, nicht mit einem Nullzeiger eines anderen Typs abfängt, wie z(void*)0
. Aber ich kann glauben, dass es einige Verwendungszwecke hat, auch wenn Sie nur einen eigenen einwertigen Platzhaltertyp definieren müssen, der "keine" bedeutet.nullptr
einen genau definierten numerischen Wert hat, während dies bei Nullzeigerkonstanten nicht der Fall ist. Eine Nullzeigerkonstante wird in den Nullzeiger dieses Typs konvertiert (was auch immer das ist). Es ist erforderlich, dass zwei Nullzeiger desselben Typs identisch verglichen werden, und die boolesche Konvertierung verwandelt einen Nullzeiger infalse
. Sonst ist nichts erforderlich. Daher ist es für einen Compiler (albern, aber möglich) möglich, zB0xabcdef1234
oder eine andere Zahl für den Nullzeiger zu verwenden. Andererseitsnullptr
ist eine Konvertierung in eine numerische Null erforderlich.f(nullptr)
wird nicht die beabsichtigte Funktion aufrufen? Es gab mehr als eine Motivation. Viele andere nützliche Dinge können die Programmierer in den kommenden Jahren selbst entdecken. Man kann also nicht sagen, dass es nur eine wahre Verwendung von gibtnullptr
.C ++ 11
nullptr
wird alsNull
Zeigerkonstante bezeichnet. Es verbessert die Typensicherheit und löst mehrdeutige Situationen im Gegensatz zur vorhandenen implementierungsabhängigen NullzeigerkonstanteNULL
. Um die Vorteile von verstehen zu könnennullptr
. Wir müssen zuerst verstehen, was istNULL
und welche Probleme damit verbunden sind.Was ist
NULL
genau?Pre C ++ 11
NULL
wurde verwendet, um einen Zeiger darzustellen, der keinen Wert hat, oder einen Zeiger, der auf nichts Gültiges zeigt. Entgegen der weit verbreiteten VorstellungNULL
ist in C ++ kein Schlüsselwort . Es ist eine Kennung, die in Standardbibliotheksüberschriften definiert ist. Kurz gesagt, Sie können nicht verwenden,NULL
ohne einige Standardbibliotheksheader einzuschließen. Betrachten Sie das Beispielprogramm :Ausgabe:
Der C ++ - Standard definiert NULL als ein implementierungsdefiniertes Makro, das in bestimmten Standardbibliotheksheaderdateien definiert ist. Der Ursprung von NULL ist von C und C ++ hat ihn von C geerbt. Der C-Standard hat NULL als
0
oder definiert(void *)0
. In C ++ gibt es jedoch einen subtilen Unterschied.C ++ konnte diese Spezifikation nicht akzeptieren. Im Gegensatz zu C ist C ++ eine stark typisierte Sprache (C erfordert keine explizite Umwandlung
void*
in einen beliebigen Typ, während C ++ eine explizite Umwandlung vorschreibt ). Dies macht die vom C-Standard angegebene Definition von NULL in vielen C ++ - Ausdrücken unbrauchbar. Beispielsweise:Wenn NULL als definiert wäre
(void *)0
, würde keiner der obigen Ausdrücke funktionieren.void *
bis erforderlich iststd::string
.void *
zum Zeiger auf die Elementfunktion erforderlich ist.Im Gegensatz zu C hat C ++ Standard den Auftrag, NULL als numerisches Literal
0
oder zu definieren0L
.Was ist also die Notwendigkeit für eine weitere Nullzeigerkonstante, wenn wir
NULL
bereits haben?Obwohl das C ++ - Standardkomitee eine NULL-Definition ausgearbeitet hat, die für C ++ funktioniert, hatte diese Definition einen angemessenen Anteil an Problemen. NULL funktionierte für fast alle Szenarien gut genug, aber nicht für alle. Es gab überraschende und fehlerhafte Ergebnisse für bestimmte seltene Szenarien. Zum Beispiel :
Ausgabe:
Natürlich scheint die Absicht zu sein, die Version aufzurufen, die
char*
als Argument verwendet wird, aber wie die Ausgabe zeigt, wird die Funktionint
aufgerufen, die eine Version benötigt. Dies liegt daran, dass NULL ein numerisches Literal ist.Da durch die Implementierung definiert ist, ob NULL 0 oder 0L ist, kann die Auflösung der Funktionsüberlastung zu großer Verwirrung führen.
Beispielprogramm:
Analyse des obigen Snippets:
doSomething(char *)
wie erwartet.doSomething(int)
aber möglicherweise war diechar*
Version erwünscht, da0
IS auch ein Nullzeiger ist.NULL
definiert als0
, werden AufrufedoSomething(int)
angezeigt , wenn dies möglicherweisedoSomething(char *)
beabsichtigt war, was möglicherweise zu einem Logikfehler zur Laufzeit führt. WennNULL
definiert als0L
, ist der Aufruf mehrdeutig und führt zu einem Kompilierungsfehler.Abhängig von der Implementierung kann derselbe Code verschiedene Ergebnisse liefern, was eindeutig unerwünscht ist. Natürlich wollte das C ++ - Standardkomitee dies korrigieren, und das ist die Hauptmotivation für nullptr.
Was ist
nullptr
und wie vermeidet es die Probleme vonNULL
?C ++ 11 führt ein neues Schlüsselwort ein
nullptr
, das als Nullzeigerkonstante dient. Im Gegensatz zu NULL ist sein Verhalten nicht implementierungsdefiniert. Es ist kein Makro, aber es hat einen eigenen Typ. nullptr hat den Typstd::nullptr_t
. C ++ 11 definiert die Eigenschaften für nullptr entsprechend, um die Nachteile von NULL zu vermeiden. Um seine Eigenschaften zusammenzufassen:Eigenschaft 1: Es hat einen eigenen Typ
std::nullptr_t
undEigenschaft 2: Es ist implizit konvertierbar und mit jedem Zeigertyp oder Zeiger-zu-Element-Typ vergleichbar, aber
Eigenschaft 3: Es ist nicht implizit konvertierbar oder mit integralen Typen vergleichbar, außer für
bool
.Betrachten Sie das folgende Beispiel:
Im obigen Programm
char *
Version, Eigenschaft 2 & 3 aufSomit vermeidet die Einführung von nullptr alle Probleme des guten alten NULL.
Wie und wo solltest du verwenden
nullptr
?Die Faustregel für C ++ 11 lautet: Beginnen
nullptr
Sie einfach mit der Verwendung, wenn Sie in der Vergangenheit sonst NULL verwendet hätten.Standardreferenzen:
C ++ 11 Standard: C.3.2.4 Makro NULL
C ++ 11 Standard: 18.2 Typen
C ++ 11 Standard: 4.10
Zeigerkonvertierungen C99 Standard: 6.3.2.3 Zeiger
quelle
nullptr
, obwohl ich nicht wusste, welchen Unterschied er wirklich zu meinem Code macht. Vielen Dank für die tolle Antwort und vor allem für die Mühe. Brachte mir viel Licht auf das Thema.0xccccc....
eine wertlose Variable ist ein inhärenter Widerspruch.bool flag = nullptr;
). Nein, nicht OK, ich erhalte den folgenden Fehler beim Kompilieren mit g ++ 6:error: converting to ‘bool’ from ‘std::nullptr_t’ requires direct-initialization [-fpermissive]
Die eigentliche Motivation ist hier die perfekte Weiterleitung .
Erwägen:
Einfach ausgedrückt ist 0 ein spezieller Wert , aber Werte können sich nicht über die Systemtypen verbreiten. Weiterleitungsfunktionen sind unerlässlich, und 0 kann nicht damit umgehen. Daher war es absolut notwendig einzuführen
nullptr
, wo der Typ das Besondere ist und der Typ sich tatsächlich ausbreiten kann. Tatsächlich musste das MSVC-Teamnullptr
vorzeitig einführen, nachdem es rvalue-Referenzen implementiert und diese Falle für sich entdeckt hatte.Es gibt einige andere
nullptr
Eckfälle, in denen das Leben leichter gemacht werden kann - aber es ist kein Kernfall, da eine Besetzung diese Probleme lösen kann. ErwägenRuft zwei separate Überladungen auf. Darüber hinaus berücksichtigen
Das ist nicht eindeutig. Mit nullptr können Sie jedoch angeben
quelle
forward((int*)0)
funktioniert. Vermisse ich etwasGrundlagen von nullptr
std::nullptr_t
ist der Typ des Nullzeigerliterals nullptr. Es ist ein Wert vom Typ prvalue / rvaluestd::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:
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:
Die Verwendung von nullptr anstelle von 0 oder NULL vermeidet somit Überraschungen bei der Überlastungsauflösung.
Ein weiterer Vorteil
nullptr
gegenüber derNULL(0)
Verwendung von Auto für den RückgabetypAngenommen, Sie stoßen in einer Codebasis auf Folgendes:
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,
Es gibt keine Mehrdeutigkeit: Das Ergebnis muss ein Zeigertyp sein.
Vorteil 3
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 VorlagenfunktionlockAndCall
anstelle einer MehrfachdefinitionlockAndCallF1, lockAndCallF2 & lockAndCallF3
für redundanten Code geschrieben.Der Code wird wie folgt umgerechnet:
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 istf1
. Die im Aufruf an übergebene 0lockAndCall
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 vonlockAndCall
with 0 schlägt fehl, da innerhalb der Vorlage ein int an eine Funktion übergeben wird, für die a erforderlich iststd::shared_ptr<int>
.Die Analyse für den Anruf
NULL
ist im Wesentlichen dieselbe. Bei derNULL
Übergabe anlockAndCall
wird ein integraler Typ für den Parameter ptr abgeleitet, und ein Typfehler tritt auf, wennptr
- ein int- oder int-ähnlicher Typ - an übergeben wirdf2
, der erwartet, dass a erhalten wirdstd::unique_ptr<int>
.Im Gegensatz dazu hat der Anruf
nullptr
keine Probleme. Wenn an übergebennullptr
wird,lockAndCall
wird der Typ fürptr
als abgeleitetstd::nullptr_t
. Wenn an übergebenptr
wird,f3
erfolgt eine implizite Konvertierung vonstd::nullptr_t
nachint*
, dastd::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
.quelle
Es gibt keinen direkten Vorteil, wenn
nullptr
Sie die Beispiele so gezeigt haben.Stellen Sie sich jedoch eine Situation vor, in der Sie zwei Funktionen mit demselben Namen haben. Ich nehme
int
und noch eineint*
Wenn Sie mit
foo(int*)
NULL anrufen möchten, lautet der Weg wie folgt:nullptr
macht es einfacher und intuitiver :Zusätzlicher Link von Bjarnes Webseite.
Irrelevant, aber auf C ++ 11 Randnotiz:
quelle
decltype(nullptr)
iststd::nullptr_t
.typedef decltype(nullptr) nullptr_t;
. Ich denke, ich kann im Standard schauen. Ah, ich habe esnullptr
.Wie andere bereits gesagt haben, liegt der Hauptvorteil in Überlastungen. Und während explizite
int
Überladungen imstd::fill
Vergleich zu Zeigern selten sein können, sollten Sie Standardbibliotheksfunktionen wie (die mich in C ++ 03 mehr als einmal gebissen haben) berücksichtigen :Kompiliert nicht :
Cannot convert int to MyClass*
.quelle
IMO ist wichtiger als diese Überlastungsprobleme: In tief verschachtelten Vorlagenkonstrukten ist es schwierig, den Überblick über die Typen nicht zu verlieren, und explizite Signaturen sind ein ziemliches Unterfangen. Je genauer Sie sich auf den beabsichtigten Zweck konzentrieren, desto besser wird für alles, was Sie verwenden, der Bedarf an expliziten Signaturen verringert, und der Compiler kann aufschlussreichere Fehlermeldungen erstellen, wenn etwas schief geht.
quelle