Ich verstehe die Syntax und die allgemeine Semantik von Zeigern gegenüber Referenzen, aber wie soll ich entscheiden, wann es mehr oder weniger angemessen ist, Referenzen oder Zeiger in einer API zu verwenden?
Natürlich benötigen einige Situationen die eine oder andere ( operator++
benötigt ein Referenzargument), aber im Allgemeinen bevorzuge ich Zeiger (und konstante Zeiger), da die Syntax klar ist, dass die Variablen destruktiv übergeben werden.
ZB im folgenden Code:
void add_one(int& n) { n += 1; }
void add_one(int* const n) { *n += 1; }
int main() {
int a = 0;
add_one(a); // Not clear that a may be modified
add_one(&a); // 'a' is clearly being passed destructively
}
Mit dem Zeiger ist es immer (offensichtlicher), was los ist. Sind APIs und APIs, bei denen Klarheit ein großes Problem darstellt, Zeiger nicht geeigneter als Referenzen? Bedeutet das, dass Referenzen nur bei Bedarf verwendet werden sollten (z. B. operator++
)? Gibt es Leistungsprobleme mit dem einen oder anderen?
BEARBEITEN (veraltet):
Neben dem Zulassen von NULL-Werten und dem Umgang mit Raw-Arrays scheint die Wahl von den persönlichen Vorlieben abhängig zu sein. Ich habe die folgende Antwort akzeptiert, die auf den C ++ - Style Guide von Google verweist , da sie die Ansicht enthält, dass "Verweise verwirrend sein können, da sie eine Wertsyntax, aber eine Zeigersemantik haben".
Aufgrund der zusätzlichen Arbeit, die erforderlich ist, um Zeigerargumente zu bereinigen, die nicht NULL sein sollten (z. B. add_one(0)
wird die Zeigerversion aufgerufen und zur Laufzeit unterbrochen), ist es aus Sicht der Wartbarkeit sinnvoll, Referenzen zu verwenden, bei denen ein Objekt vorhanden sein muss, obwohl dies eine Schande ist die syntaktische Klarheit zu verlieren.
add_one(a);
ist unklar, oba
das geändert wird? Es steht direkt im Code: Fügen Sie eine hinzu .addOneTo(...)
. Wenn Sie das nicht möchten, schauen Sie sich einfach die Erklärung an.Antworten:
Verwenden Sie Referenz, wo immer Sie können, Zeiger, wo immer Sie müssen.
Vermeiden Sie Zeiger, bis Sie nicht mehr können.
Der Grund dafür ist, dass Zeiger das Verfolgen / Lesen erschweren, weniger sicher und weitaus gefährlicher sind als alle anderen Konstrukte.
Als Faustregel gilt also, Zeiger nur dann zu verwenden, wenn es keine andere Wahl gibt.
Das Zurückgeben eines Zeigers auf ein Objekt ist beispielsweise eine gültige Option, wenn die Funktion in einigen Fällen nullptr zurückgeben kann und davon ausgegangen wird, dass dies der Fall ist. Eine bessere Option wäre jedoch, etwas Ähnliches zu verwenden
boost::optional
.Ein weiteres Beispiel ist die Verwendung von Zeigern auf den Rohspeicher für bestimmte Speichermanipulationen. Dies sollte in sehr engen Teilen des Codes versteckt und lokalisiert werden, um die gefährlichen Teile der gesamten Codebasis zu begrenzen.
In Ihrem Beispiel macht es keinen Sinn, einen Zeiger als Argument zu verwenden, weil:
nullptr
als Argument angeben, gehen Sie in ein Land mit undefiniertem Verhalten.Wenn das Verhalten der Funktion mit oder ohne ein bestimmtes Objekt funktionieren müsste, deutet die Verwendung eines Zeigers als Attribut darauf hin, dass Sie
nullptr
als Argument übergeben können, und dies ist für die Funktion in Ordnung. Das ist eine Art Vertrag zwischen dem Benutzer und der Implementierung.quelle
add_one(a)
das Ergebnis nicht zurückgegeben werden, anstatt es als Referenz festzulegen?add_one(a)
es verwirrend ist, dann liegt das daran, dass es falsch benannt ist.add_one(&a)
hätte die gleiche Verwirrung, nur jetzt könnten Sie den Zeiger und nicht das Objekt inkrementieren.add_one_inplace(a)
würde jede Verwirrung vermeiden.NULL
undnullptr
, und es hat sie für einen Grund. Und es ist kein wohlüberlegter oder sogar realistischer Ratschlag, "niemals Zeiger verwenden" und / oder "niemals NULL verwenden, immer verwendenboost::optional
" zu geben. Das ist einfach verrückt. Versteh mich nicht falsch, rohe Zeiger werden in C ++ seltener benötigt als in C, aber sie sind nützlich, sie sind nicht so "gefährlich", wie manche C ++ - Leute gerne behaupten (das ist auch übertrieben), und noch einmal: wenn es einfacher ist, nur einen Zeiger zu verwenden undreturn nullptr;
einen fehlenden Wert anzuzeigen ... Warum den gesamten Boost importieren?if
ist veraltet und sollte manwhile() { break; }
stattdessen verwenden, oder? Keine Sorge, ich habe große Codebasen gesehen und damit gearbeitet, und ja, wenn Sie nachlässig sind , ist der Besitz ein Problem. Nicht, wenn Sie sich an Konventionen halten, diese konsequent verwenden und Ihren Code kommentieren und dokumentieren. Aber ich sollte doch einfach C verwenden, weil ich für C ++ zu dumm bin, oder?Die Leistungen sind genau gleich, da Referenzen intern als Zeiger implementiert werden. Sie brauchen sich also keine Sorgen zu machen.
Es gibt keine allgemein anerkannte Konvention bezüglich der Verwendung von Referenzen und Zeigern. In einigen Fällen müssen Sie Referenzen zurückgeben oder akzeptieren (z. B. Kopierkonstruktor), aber ansonsten können Sie tun, was Sie möchten. Eine häufig vorkommende Konvention ist die Verwendung von Referenzen, wenn der Parameter auf ein vorhandenes Objekt verweisen muss, und von Zeigern, wenn ein NULL-Wert in Ordnung ist.
Einige Codierungskonventionen (wie die von Google ) schreiben vor, dass immer Zeiger oder konstante Referenzen verwendet werden sollten, da Referenzen eine etwas unklare Syntax haben: Sie haben Referenzverhalten, aber Wertesyntax.
quelle
add_one
ist gebrochen :add_one(0); // passing a perfectly valid pointer value
, kaboom. Sie müssen überprüfen, ob es null ist. Einige Leute werden erwidern: "Nun, ich werde nur dokumentieren, dass meine Funktion mit null nicht funktioniert." Das ist in Ordnung, aber dann besiegen Sie den Zweck der Frage: Wenn Sie in der Dokumentation nachsehen, ob null in Ordnung ist, sehen Sie auch die Funktionsdeklaration .int& i = *((int*)0);
. Dies ist keine gültige Retorte. Das Problem im vorherigen Code liegt in der Verwendung des Zeigers, nicht in der Referenz . Referenzen sind niemals null, Punkt.Aus C ++ FAQ Lite -
quelle
C with class
(was nicht C ++ ist).Meine Faustregel lautet:
&
)const
dass es sich um einen eingehenden Parameter handelt.)const T&
).int ¤t = someArray[i]
)Unabhängig davon, welche Sie verwenden, vergessen Sie nicht, Ihre Funktionen und die Bedeutung ihrer Parameter zu dokumentieren, wenn sie nicht offensichtlich sind.
quelle
Haftungsausschluss: Abgesehen von der Tatsache, dass Referenzen weder NULL noch "Rebound" sein können (was bedeutet, dass sie das Objekt, dessen Alias sie sind, nicht ändern können), kommt es wirklich auf den Geschmack an, also werde ich nicht sagen "das ist besser".
Trotzdem stimme ich Ihrer letzten Aussage im Beitrag nicht zu, da ich nicht glaube, dass der Code durch Verweise an Klarheit verliert. In Ihrem Beispiel
könnte klarer sein als
da Sie wissen, dass sich der Wert von a höchstwahrscheinlich ändern wird. Auf der anderen Seite jedoch die Signatur der Funktion
ist auch etwas unklar: wird n eine einzelne ganze Zahl oder ein Array sein? Manchmal haben Sie nur Zugriff auf (schlecht dokumentierte) Header und Signaturen wie
sind auf den ersten Blick nicht leicht zu interpretieren.
Imho, Referenzen sind so gut wie Zeiger, wenn keine (Neu-) Zuordnung oder Neubindung (im zuvor erläuterten Sinne) erforderlich ist. Wenn ein Entwickler nur Zeiger für Arrays verwendet, sind Funktionssignaturen etwas weniger mehrdeutig. Ganz zu schweigen von der Tatsache, dass die Operatorsyntax mit Referenzen viel besser lesbar ist.
quelle
Wie andere bereits geantwortet: Verwenden Sie immer Referenzen, es sei denn, die Variable ist
NULL
/nullptr
ist wirklich ein gültiger Zustand.John Carmacks Standpunkt zu diesem Thema ist ähnlich:
http://www.altdevblogaday.com/2011/12/24/static-code-analysis/
Bearbeiten 2012-03-13
User Bret Kuhns bemerkt zu Recht:
Richtig, aber die Frage bleibt auch dann bestehen, wenn Rohzeiger durch intelligente Zeiger ersetzt werden.
Zum Beispiel können beide
std::unique_ptr
undstd::shared_ptr
als "leere" Zeiger über ihren Standardkonstruktor konstruiert werden:... was bedeutet, dass ihre Verwendung ohne Überprüfung, dass sie nicht leer sind, einen Absturz riskiert, und genau darum geht es in der Diskussion von J. Carmack.
Und dann haben wir das amüsante Problem: "Wie übergeben wir einen intelligenten Zeiger als Funktionsparameter?"
Jon ‚s Antwort auf die Frage C ++ - Verweise auf boost :: shared_ptr vorbei , und die folgenden Kommentare , die zeigen , selbst dann, vorbei an ein Smart - Pointer durch Kopie oder durch Verweis ist nicht so eindeutig, wie man möchte (Ich bevorzuge ich das" by-reference "standardmäßig, aber ich könnte mich irren).
quelle
shared_ptr
, und perfekt funktionieren sollteunique_ptr
. Die Besitzersemantik und In / Out-Parameterkonventionen werden durch eine Kombination dieser drei Teile und der Konstanz sichergestellt. In C ++ sind fast keine Rohzeiger erforderlich, außer wenn es sich um Legacy-Code und sehr optimierte Algorithmen handelt. Die Bereiche, in denen sie verwendet werden, sollten so gekapselt wie möglich sein und alle Rohzeiger in das semantisch angemessene "moderne" Äquivalent umwandeln.Es ist keine Geschmackssache. Hier sind einige endgültige Regeln.
Wenn Sie auf eine statisch deklarierte Variable innerhalb des Bereichs verweisen möchten, in dem sie deklariert wurde, verwenden Sie eine C ++ - Referenz, und sie ist absolut sicher. Gleiches gilt für einen statisch deklarierten Smart Pointer. Das Übergeben von Parametern als Referenz ist ein Beispiel für diese Verwendung.
Wenn Sie auf etwas aus einem Bereich verweisen möchten, der breiter ist als der Bereich, in dem es deklariert ist, sollten Sie einen intelligenten Zeiger mit Referenzzählung verwenden, damit er absolut sicher ist.
Sie können aus syntaktischen Gründen auf ein Element einer Sammlung mit einer Referenz verweisen, dies ist jedoch nicht sicher. Das Element kann jederzeit gelöscht werden.
Um eine Referenz auf ein Element einer Sammlung sicher zu halten, müssen Sie einen intelligenten Zeiger mit Referenzzählung verwenden.
quelle
Jeder Leistungsunterschied wäre so gering, dass es nicht gerechtfertigt wäre, den weniger klaren Ansatz zu verwenden.
Erstens ist ein Fall, der nicht erwähnt wurde, wo Referenzen im Allgemeinen überlegen sind,
const
Referenzen. Bei nicht einfachen Typen wird a übergebenconst reference
vermeidet das das Erstellen eines temporären und verursacht keine Verwirrung, über die Sie sich Sorgen machen (da der Wert nicht geändert wird). Wenn Sie eine Person zwingen, einen Zeiger zu übergeben, ist dies sehr verwirrend, da Sie möglicherweise glauben, dass sich der Wert geändert hat, wenn Sie die Adresse sehen, die an eine Funktion übergeben wurde.Auf jeden Fall stimme ich Ihnen grundsätzlich zu. Ich mag es nicht, wenn Funktionen Referenzen verwenden, um ihren Wert zu ändern, wenn es nicht sehr offensichtlich ist, dass die Funktion dies tut. Auch ich bevorzuge in diesem Fall Zeiger.
Wenn Sie einen Wert in einem komplexen Typ zurückgeben müssen, bevorzuge ich Referenzen. Zum Beispiel:
Hier macht der Funktionsname deutlich, dass Sie Informationen in einem Array zurückerhalten. Es gibt also keine Verwirrung.
Die Hauptvorteile von Referenzen sind, dass sie immer einen gültigen Wert enthalten, sauberer als Zeiger sind und Polymorphismus unterstützen, ohne dass eine zusätzliche Syntax erforderlich ist. Wenn keiner dieser Vorteile zutrifft, gibt es keinen Grund, eine Referenz einem Zeiger vorzuziehen.
quelle
Aus dem Wiki kopiert -
Ich stimme dem zu 100% zu, und deshalb glaube ich, dass Sie eine Referenz nur verwenden sollten, wenn Sie einen sehr guten Grund dafür haben.
quelle
Zu beachtende Punkte:
Zeiger können sein
NULL
, Referenzen können nicht seinNULL
.Referenzen sind einfacher zu verwenden,
const
können als Referenz verwendet werden, wenn wir den Wert nicht ändern möchten und nur eine Referenz in einer Funktion benötigen.Zeiger verwendet mit einer
*
Weile Referenzen verwendet mit a&
.Verwenden Sie Zeiger, wenn eine arithmetische Zeigeroperation erforderlich ist.
Sie können Zeiger auf einen Leertyp haben,
int a=5; void *p = &a;
aber keinen Verweis auf einen Leertyp.Zeiger gegen Referenz
Urteil, wann was zu verwenden ist
Zeiger : Für Array-, Linklisten-, Baumimplementierungen und Zeigerarithmetik.
Referenz : In Funktionsparametern und Rückgabetypen.
quelle
Es gibt ein Problem mit der Regel " Verweise verwenden, wo immer möglich ", und es tritt auf, wenn Sie die Referenz für die weitere Verwendung aufbewahren möchten. Stellen Sie sich vor, Sie haben folgende Klassen, um dies anhand eines Beispiels zu veranschaulichen.
Auf den ersten Blick scheint es eine gute Idee zu sein, Parameter im
RefPhone(const SimCard & card)
Konstruktor von einer Referenz übergeben zu lassen, da dadurch verhindert wird, dass falsche / Null-Zeiger an den Konstruktor übergeben werden. Es fördert irgendwie die Zuweisung von Variablen auf dem Stapel und die Nutzung von RAII.Aber dann kommen Provisorien, um deine glückliche Welt zu zerstören.
Wenn Sie sich also blind an Referenzen halten, tauschen Sie die Möglichkeit, ungültige Zeiger zu übergeben, gegen die Möglichkeit aus, Referenzen auf zerstörte Objekte zu speichern, was im Grunde den gleichen Effekt hat.
Bearbeiten: Beachten Sie, dass ich mich an die Regel "Verwenden Sie Referenz, wo immer Sie können, Zeiger, wo immer Sie müssen. Vermeiden Sie Zeiger, bis Sie nicht können." von der am besten bewerteten und akzeptierten Antwort (andere Antworten schlagen dies ebenfalls vor). Obwohl es offensichtlich sein sollte, soll ein Beispiel nicht zeigen, dass Referenzen als solche schlecht sind. Sie können jedoch wie Zeiger missbraucht werden und ihre eigenen Bedrohungen in den Code einbringen.
Es gibt folgende Unterschiede zwischen Zeigern und Referenzen.
Unter Berücksichtigung dieser sind meine aktuellen Regeln wie folgt.
quelle
const SimCard & m_card;
ist nur ein schlecht geschriebener Code.const SimCard & m_card
es richtig ist oder nicht, hängt vom Kontext ab. Die Nachricht in diesem Beitrag ist nicht, dass Referenzen unsicher sind (obwohl sie sein können, wenn man sich bemüht). Die Botschaft ist, dass Sie sich nicht blind an das Mantra "Referenzen verwenden, wann immer möglich" halten sollten. Ein Beispiel ist das Ergebnis einer aggressiven Verwendung der Doktrin "Referenzen verwenden, wann immer dies möglich ist". Dies sollte klar sein.const SimCard & m_card
. Wenn Sie mit temporären Elementen effizient arbeiten möchten, fügen Sie einenexplicit RefPhone(const SimCard&& card)
Konstruktor hinzu.Im Folgenden finden Sie einige Richtlinien.
Eine Funktion verwendet übergebene Daten, ohne sie zu ändern:
Wenn das Datenobjekt klein ist, z. B. ein integrierter Datentyp oder eine kleine Struktur, übergeben Sie es als Wert.
Wenn das Datenobjekt ein Array ist, verwenden Sie einen Zeiger, da dies Ihre einzige Wahl ist. Machen Sie den Zeiger zu einem Zeiger auf const.
Wenn das Datenobjekt eine Struktur mit guter Größe ist, verwenden Sie einen const-Zeiger oder eine const-Referenz, um die Programmeffizienz zu erhöhen. Sie sparen Zeit und Platz, die zum Kopieren einer Struktur oder eines Klassendesigns erforderlich sind. Machen Sie den Zeiger oder die Referenz const.
Wenn das Datenobjekt ein Klassenobjekt ist, verwenden Sie eine konstante Referenz. Die Semantik des Klassenentwurfs erfordert häufig die Verwendung einer Referenz. Dies ist der Hauptgrund, warum C ++ diese Funktion hinzugefügt hat. Daher ist die Standardmethode zum Übergeben von Klassenobjektargumenten die Referenz.
Eine Funktion ändert Daten in der aufrufenden Funktion:
1.Wenn das Datenobjekt ein integrierter Datentyp ist, verwenden Sie einen Zeiger. Wenn Sie Code wie fixit (& x) erkennen, wobei x ein int ist, ist es ziemlich klar, dass diese Funktion beabsichtigt, x zu ändern.
2.Wenn das Datenobjekt ein Array ist, verwenden Sie Ihre einzige Auswahl: einen Zeiger.
3.Wenn das Datenobjekt eine Struktur ist, verwenden Sie eine Referenz oder einen Zeiger.
4.Wenn das Datenobjekt ein Klassenobjekt ist, verwenden Sie eine Referenz.
Dies sind natürlich nur Richtlinien, und es kann Gründe geben, unterschiedliche Entscheidungen zu treffen. Beispielsweise verwendet cin Referenzen für Basistypen, sodass Sie cin >> n anstelle von cin >> & n verwenden können.
quelle
Referenzen sind sauberer und einfacher zu verwenden und können Informationen besser verbergen. Referenzen können jedoch nicht neu zugewiesen werden. Wenn Sie zuerst auf ein Objekt und dann auf ein anderes zeigen müssen, müssen Sie einen Zeiger verwenden. Referenzen können nicht null sein. Wenn also die Möglichkeit besteht, dass das betreffende Objekt null ist, dürfen Sie keine Referenz verwenden. Sie müssen einen Zeiger verwenden. Wenn Sie die Objektmanipulation selbst durchführen möchten, dh wenn Sie Speicherplatz für ein Objekt auf dem Heap und nicht auf dem Stapel zuweisen möchten, müssen Sie den Zeiger verwenden
quelle
Ihr richtig geschriebenes Beispiel sollte so aussehen
Deshalb sind Referenzen möglichst vorzuziehen ...
quelle
Ich habe gerade einen Cent reingelegt. Ich habe gerade einen Test durchgeführt. Ein höhnischer dazu. Ich habe g ++ einfach die Assembly-Dateien desselben Mini-Programms mit Zeigern erstellen lassen, verglichen mit Referenzen. Bei der Betrachtung der Ausgabe sind sie genau gleich. Anders als die Symbolbenennung. Wenn man sich also die Leistung ansieht (in einem einfachen Beispiel), gibt es kein Problem.
Nun zum Thema Zeiger gegen Referenzen. IMHO denke ich, dass Klarheit über allem steht. Sobald ich implizites Verhalten lese, beginnen sich meine Zehen zu kräuseln. Ich bin damit einverstanden, dass es ein schönes implizites Verhalten ist, dass eine Referenz nicht NULL sein kann.
Das Dereferenzieren eines NULL-Zeigers ist nicht das Problem. Es wird Ihre Anwendung zum Absturz bringen und ist einfach zu debuggen. Ein größeres Problem sind nicht initialisierte Zeiger mit ungültigen Werten. Dies führt höchstwahrscheinlich zu einer Speicherbeschädigung, die ein undefiniertes Verhalten ohne eindeutigen Ursprung verursacht.
Hier sind Referenzen meiner Meinung nach viel sicherer als Zeiger. Und ich stimme einer früheren Aussage zu, dass die Schnittstelle (die klar dokumentiert werden sollte, siehe Design by Contract, Bertrand Meyer) das Ergebnis der Parameter für eine Funktion definiert. Wenn ich dies alles berücksichtige, gehe ich vor, wo immer / wann immer möglich, Referenzen zu verwenden.
quelle
Für Zeiger müssen sie auf etwas zeigen, sodass Zeiger Speicherplatz kosten.
Beispielsweise nimmt eine Funktion, die einen Ganzzahlzeiger verwendet, die Ganzzahlvariable nicht an. Sie müssen also zuerst einen Zeiger erstellen, um ihn an die Funktion weiterzugeben.
Als Referenz kostet es keinen Speicher. Sie haben eine Ganzzahlvariable und können diese als Referenzvariable übergeben. Das ist es. Sie müssen keine Referenzvariable speziell dafür erstellen.
quelle
&address
. Eine Referenz kostet sicherlich Speicher, wenn sie Mitglied eines Objekts ist, und außerdem implementieren alle vorhandenen Compiler Referenzen tatsächlich als Adressen, sodass Sie weder beim Übergeben von Parametern noch bei der Dereferenzierung etwas sparen.