Welche Vorteile bietet das Übergeben eines Zeigers gegenüber dem Übergeben eines Verweises in C ++?
In letzter Zeit habe ich eine Reihe von Beispielen gesehen, bei denen die Übergabe von Funktionsargumenten durch Zeiger anstelle der Übergabe als Referenz ausgewählt wurde. Gibt es Vorteile dabei?
Beispiel:
func(SPRITE *x);
mit einem Anruf von
func(&mySprite);
vs.
func(SPRITE &x);
mit einem Anruf von
func(mySprite);
c++
pointers
parameter-passing
pass-by-reference
Matt Pascoe
quelle
quelle
new
, einen Zeiger und die daraus resultierenden Eigentumsfragen zu erstellen.Antworten:
Ein Zeiger kann einen NULL-Parameter empfangen, ein Referenzparameter nicht. Wenn die Möglichkeit besteht, dass Sie "kein Objekt" übergeben möchten, verwenden Sie einen Zeiger anstelle einer Referenz.
Durch Übergeben eines Zeigers können Sie außerdem an der Aufrufstelle explizit sehen, ob das Objekt als Wert oder als Referenz übergeben wird:
quelle
func2 passes by reference
. Ich weiß zwar zu schätzen, dass Sie gemeint haben, dass es aus einer übergeordneten Perspektive "als Referenz" übergeben wird, implementiert durch Übergeben eines Zeigers auf einer Codeebene , aber dies war sehr verwirrend (siehe stackoverflow.com/questions/13382356/… ).func(int& a)
ist in keiner Version des Standards gültig. Sie kompilieren Ihre Dateien wahrscheinlich versehentlich als C ++.Übergabe durch Zeiger
nothing
. Dies kann verwendet werden, um optionale Argumente bereitzustellen.Als Referenz übergeben
string s = &str1 + &str2;
Zeiger verwenden.void f(const T& t); ... f(T(a, b, c));
, Zeiger können nicht so verwendet werden, da Sie die Adresse eines temporären nicht annehmen können.quelle
Ich mag die Argumentation eines Artikels von "cplusplus.com:"
Was ich daraus nehme, ist, dass der Hauptunterschied zwischen der Wahl eines Zeigers oder eines Referenzparameters darin besteht, ob NULL ein akzeptabler Wert ist. Das ist es.
Ob der Wert eingegeben, ausgegeben, geändert usw. werden kann, sollte schließlich in der Dokumentation / den Kommentaren zur Funktion enthalten sein.
quelle
Allen Holubs "Genug Seil, um sich in den Fuß zu schießen" listet die folgenden 2 Regeln auf:
Er listet mehrere Gründe auf, warum Referenzen zu C ++ hinzugefügt wurden:
const
Mit Referenzen können Sie eine Semantik für die Übergabe von Werten verwenden und gleichzeitig eine Kopie vermeidenSein Hauptpunkt ist, dass Referenzen nicht als Ausgabeparameter verwendet werden sollten, da an der Aufrufstelle kein Hinweis darauf vorhanden ist, ob der Parameter eine Referenz oder ein Wertparameter ist. Seine Regel ist es also, nur
const
Referenzen als Argumente zu verwenden.Persönlich halte ich dies für eine gute Faustregel, da dadurch klarer wird, ob ein Parameter ein Ausgabeparameter ist oder nicht. Obwohl ich dem im Allgemeinen persönlich zustimme, erlaube ich mir, mich von den Meinungen anderer in meinem Team beeinflussen zu lassen, wenn sie für Ausgabeparameter als Referenz argumentieren (einige Entwickler mögen sie sehr).
quelle
Erläuterungen zu den vorhergehenden Beiträgen:
Referenzen sind KEINE Garantie dafür, dass ein Zeiger ungleich Null erhalten wird. (Obwohl wir sie oft als solche behandeln.)
Während schrecklich schlechter Code, wie Sie hinter dem schlechten Code von Woodshed herausholen , wird Folgendes kompiliert und ausgeführt: (Zumindest unter meinem Compiler.)
Das eigentliche Problem, das ich mit Referenzen habe, liegt bei anderen Programmierern, im Folgenden IDIOTS genannt , die im Konstruktor zuordnen, im Destruktor freigeben und keinen Kopierkonstruktor oder Operator = () angeben .
Plötzlich gibt es einen großen Unterschied zwischen foo (BAR Bar) und foo (BAR & Bar) . (Der automatische bitweise Kopiervorgang wird aufgerufen. Die Freigabe im Destruktor wird zweimal aufgerufen.)
Zum Glück werden moderne Compiler diese doppelte Freigabe desselben Zeigers übernehmen. Vor 15 Jahren haben sie es nicht getan. (Verwenden Sie unter gcc / g ++ setenv MALLOC_CHECK_ 0 , um die alten Methoden erneut aufzurufen .) Unter DEC UNIX wird derselbe Speicher zwei verschiedenen Objekten zugewiesen. Viel Spaß beim Debuggen ...
Praktischer:
quelle
*i
, hat Ihr Programm in dem Moment, in dem Sie sagen , ein undefiniertes Verhalten. Beispielsweise kann der Compiler diesen Code sehen und annehmen, dass "OK, dieser Code in allen Codepfaden ein undefiniertes Verhalten aufweist, sodass diese gesamte Funktion nicht erreichbar sein muss". Dann wird davon ausgegangen, dass nicht alle Zweige, die zu dieser Funktion führen, belegt sind. Dies ist eine regelmäßig durchgeführte Optimierung.Die meisten Antworten hier sprechen nicht die inhärente Mehrdeutigkeit an, einen Rohzeiger in einer Funktionssignatur zu haben, um die Absicht auszudrücken. Die Probleme sind die folgenden:
Der Aufrufer weiß nicht, ob der Zeiger auf ein einzelnes Objekt oder auf den Anfang eines "Arrays" von Objekten zeigt.
Der Aufrufer weiß nicht, ob der Zeiger den Speicher "besitzt", auf den er zeigt. IE, ob die Funktion den Speicher freigeben soll oder nicht. (
foo(new int)
- Ist das ein Speicherverlust?).Der Anrufer weiß nicht, ob er
nullptr
sicher an die Funktion übergeben werden kann oder nicht .Alle diese Probleme werden durch Referenzen gelöst:
Referenzen beziehen sich immer auf ein einzelnes Objekt.
Referenzen besitzen niemals das Gedächtnis, auf das sie sich beziehen, sie sind lediglich ein Blick in das Gedächtnis.
Referenzen dürfen nicht null sein.
Dies macht Referenzen zu einem viel besseren Kandidaten für die allgemeine Verwendung. Referenzen sind jedoch nicht perfekt - es sind einige Hauptprobleme zu berücksichtigen.
&
Operator verwenden müssen, um zu zeigen, dass wir tatsächlich einen Zeiger übergeben. Zum Beispielint a = 5; foo(a);
ist hier überhaupt nicht klar, dass a als Referenz übergeben wird und geändert werden könnte.std::optional<T&>
Zeiger (aus guten Gründen) nicht gültig sind, geben sie uns die gewünschte Nullfähigkeit.Wenn wir also eine nullfähige Referenz mit expliziter Indirektion wollen, sollten wir nach einem
T*
Recht greifen ? Falsch!Abstraktionen
In unserer Verzweiflung nach Nullfähigkeit können wir nach
T*
allen zuvor aufgeführten Mängeln und semantischen Zweideutigkeiten greifen und diese einfach ignorieren. Stattdessen sollten wir nach dem greifen, was C ++ am besten kann: einer Abstraktion. Wenn wir einfach eine Klasse schreiben, die einen Zeiger umschließt, gewinnen wir die Ausdruckskraft sowie die Nullbarkeit und explizite Indirektion.Dies ist die einfachste Oberfläche, die ich mir vorstellen kann, aber sie erledigt den Job effektiv. Sie können die Referenz initialisieren, prüfen, ob ein Wert vorhanden ist, und auf den Wert zugreifen. Wir können es so verwenden:
Wir haben unsere Ziele erreicht! Lassen Sie uns nun die Vorteile im Vergleich zum Rohzeiger sehen.
nullptr
kann, da der Funktionsautor explizit nach einem fragtoptional_ref
Wir könnten die Schnittstelle von hier aus komplexer gestalten, z. B. durch Hinzufügen von Gleichheitsoperatoren, einer Monade
get_or
und einermap
Schnittstelle, einer Methode, die den Wert abruft oder eine Ausnahme auslöst,constexpr
Unterstützung. Das können Sie tun.Anstatt rohe Zeiger zu verwenden, begründen Sie, was diese Zeiger tatsächlich in Ihrem Code bedeuten, und nutzen Sie entweder eine Standard-Bibliotheksabstraktion oder schreiben Sie Ihre eigene. Dadurch wird Ihr Code erheblich verbessert.
quelle
Nicht wirklich. Intern erfolgt die Übergabe als Referenz, indem im Wesentlichen die Adresse des referenzierten Objekts übergeben wird. Es gibt also wirklich keine Effizienzgewinne durch Übergeben eines Zeigers.
Das Übergeben von Referenzen hat jedoch einen Vorteil. Es ist garantiert, dass Sie eine Instanz eines beliebigen Objekts / Typs haben, das übergeben wird. Wenn Sie einen Zeiger übergeben, besteht das Risiko, dass Sie einen NULL-Zeiger erhalten. Durch die Verwendung von Pass-by-Reference schieben Sie eine implizite NULL-Prüfung um eine Ebene an den Aufrufer Ihrer Funktion.
quelle