Wann werden Referenzen oder Zeiger verwendet?


381

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.


4
Anscheinend haben Sie bereits entschieden, welche Sie wann verwenden möchten. Persönlich übergebe ich lieber das Objekt, auf das ich reagiere, unabhängig davon, ob ich es ändere oder nicht. Wenn eine Funktion einen Zeiger verwendet, bedeutet dies, dass sie auf Zeiger einwirkt, dh sie als Iteratoren in einem Array verwendet.
Benjamin Lindley

1
@Schnommus: Fair genug, ich benutze meistens TextMate. Trotzdem halte ich es für vorzuziehen, dass die Bedeutung auf einen Blick ersichtlich ist.
Verbindung

4
Was add_one(a);ist unklar, ob adas geändert wird? Es steht direkt im Code: Fügen Sie eine hinzu .
GManNickG

32
@connec: Der Google C ++ - Styleguide wird nicht als guter C ++ - Styleguide angesehen. Es ist ein Styleguide für die Arbeit mit Googles alter C ++ - Codebasis (dh gut für ihre Sachen). Eine darauf basierende Antwort zu akzeptieren, hilft niemandem. Wenn Sie nur Ihre Kommentare und Erklärungen lesen, sind Sie mit einer bereits festgelegten Meinung zu dieser Frage gekommen und suchen nur nach anderen Personen, um Ihre Ansicht zu bestätigen. Infolgedessen stützen Sie die Frage und Antwort auf das, was Sie hören möchten / erwarten.
Martin York

1
Dies wird einfach durch Benennung der Methode behoben addOneTo(...). Wenn Sie das nicht möchten, schauen Sie sich einfach die Erklärung an.
Stefan

Antworten:


296

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:

  1. Wenn Sie nullptrals Argument angeben, gehen Sie in ein Land mit undefiniertem Verhalten.
  2. Die Referenzattributversion erlaubt das Problem mit 1 nicht (ohne leicht zu erkennende Tricks).
  3. Die Version des Referenzattributs ist für den Benutzer einfacher zu verstehen: Sie müssen ein gültiges Objekt angeben, nicht etwas, das null sein könnte.

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 nullptrals Argument übergeben können, und dies ist für die Funktion in Ordnung. Das ist eine Art Vertrag zwischen dem Benutzer und der Implementierung.


49
Ich bin nicht sicher, ob Zeiger das Lesen erschweren? Es ist ein ziemlich einfaches Konzept und macht deutlich, wann etwas wahrscheinlich geändert wird. Wenn ich sagen würde, dass es schwieriger zu lesen ist, wenn es keinen Hinweis darauf gibt, was passiert, warum sollte add_one(a)das Ergebnis nicht zurückgegeben werden, anstatt es als Referenz festzulegen?
Verbindung

46
@connec: Wenn 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.
Nicol Bolas

20
Ein Punkt, Referenzen können sich auf Speicher beziehen, der genauso leicht verschwinden kann wie Zeiger. Sie sind also nicht unbedingt sicherer als Zeiger. Das Fortbestehen und Übergeben von Referenzen kann genauso gefährlich sein wie Zeiger.
Doug T.

6
@Klaim Ich meinte rohe Zeiger. Ich meinte , dass C ++ hat Zeiger, NULLund nullptr, 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 verwenden boost::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 und return nullptr;einen fehlenden Wert anzuzeigen ... Warum den gesamten Boost importieren?

5
@Klaim "NULL zu benutzen ist eine schlechte Praxis" - das ist einfach lächerlich. Und ifist veraltet und sollte man while() { 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?

62

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.


10
Um dies ein wenig zu ergänzen, sagt der Styleguide von Google , dass Eingabeparameter für Funktionen konstante Referenzen und Ausgaben Zeiger sein sollten. Ich mag das, weil es sehr deutlich macht, wenn Sie eine Funktionssignatur lesen, was eine Eingabe und was eine Ausgabe ist.
Dan

44
@Dan: Der Google Style Guide bezieht sich auf den alten Code von Google und sollte nicht für die moderne Codierung verwendet werden. Tatsächlich ist es ein ziemlich schlechter Codierungsstil für ein neues Projekt.
GManNickG

13
@connec: Lassen Sie es mich so sagen: null ist ein perfekt gültiger Zeigerwert. Überall dort, wo es einen Zeiger gibt, kann ich ihm den Wert null geben. Ergo Ihre zweite Version add_oneist 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 .
GManNickG

8
Wenn es eine Referenz wäre, würden Sie sehen, dass dies der Fall ist. Eine solche Retorte verfehlt jedoch den Punkt: Verweise erzwingen auf Sprachebene, dass sie sich auf ein vorhandenes Objekt beziehen und möglicherweise nicht null, während Zeiger keine solche Einschränkung haben. Ich denke, es ist klar, dass die Durchsetzung auf Sprachebene leistungsfähiger und weniger fehleranfällig ist als die Durchsetzung auf Dokumentationsebene. Einige werden versuchen, darauf zu antworten, indem sie sagen: "Look, null reference : 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.
GManNickG

12
Hallo, ich habe in den Kommentaren einen Mangel an Sprachanwälten gesehen, also lassen Sie mich Abhilfe schaffen: Referenzen werden normalerweise durch Zeiger implementiert, aber der Standard sagt nichts dergleichen. Eine Implementierung unter Verwendung eines anderen Mechanismus wäre eine 100% ige Beschwerde.
Thomas Bonini

34

Aus C ++ FAQ Lite -

Verwenden Sie Referenzen, wenn Sie können, und Zeiger, wenn Sie müssen.

Referenzen werden normalerweise Zeigern vorgezogen, wenn Sie kein "erneutes Setzen" benötigen. Dies bedeutet normalerweise, dass Referenzen in der öffentlichen Schnittstelle einer Klasse am nützlichsten sind. Referenzen erscheinen normalerweise auf der Skin eines Objekts und Zeiger auf der Innenseite.

Die Ausnahme besteht darin, dass der Parameter oder Rückgabewert einer Funktion eine "Sentinel" -Referenz benötigt - eine Referenz, die sich nicht auf ein Objekt bezieht. Dies geschieht normalerweise am besten, indem Sie einen Zeiger zurückgeben / nehmen und dem NULL-Zeiger diese besondere Bedeutung geben (Referenzen müssen immer Alias-Objekte sein, kein dereferenzierter NULL-Zeiger).

Hinweis: Alte Programmierer der Zeile C mögen manchmal keine Referenzen, da sie eine Referenzsemantik bereitstellen, die im Code des Aufrufers nicht explizit ist. Nach einigen C ++ - Erfahrungen wird jedoch schnell klar, dass dies eine Form des Versteckens von Informationen ist, die eher ein Aktivposten als eine Verbindlichkeit ist. Beispielsweise sollten Programmierer Code in der Sprache des Problems und nicht in der Sprache der Maschine schreiben.


1
Ich denke, Sie könnten argumentieren, wenn Sie eine API verwenden, sollten Sie mit deren Funktionsweise vertraut sein und wissen, ob der übergebene Parameter geändert wird oder nicht ... etwas zu beachten, aber ich stimme den C-Programmierern zu ( obwohl ich selbst wenig C-Erfahrung habe). Ich möchte jedoch hinzufügen, dass eine klarere Syntax sowohl für Programmierer als auch für Maschinen von Vorteil ist.
Verbindung

1
@connec: Sicher, der C-Programmierer hat es richtig für seine Sprache. Aber machen Sie nicht den Fehler, C ++ als C zu behandeln. Es ist eine völlig andere Sprache. Wenn Sie C ++ als C behandeln, schreiben Sie am Ende das, was als gleichwertig bezeichnet wird C with class(was nicht C ++ ist).
Martin York

15

Meine Faustregel lautet:

  • Verwenden Sie Zeiger für ausgehende oder eingehende Parameter. Es ist also ersichtlich, dass der Wert geändert wird. (Sie müssen verwenden &)
  • Verwenden Sie Zeiger, wenn der Parameter NULL ein akzeptabler Wert ist. (Stellen Sie sicher, constdass es sich um einen eingehenden Parameter handelt.)
  • Verwenden Sie Referenzen für eingehende Parameter, wenn diese nicht NULL sein dürfen und kein primitiver Typ sind ( const T&).
  • Verwenden Sie Zeiger oder intelligente Zeiger, wenn Sie ein neu erstelltes Objekt zurückgeben.
  • Verwenden Sie Zeiger oder intelligente Zeiger als Struktur- oder Klassenelemente anstelle von Referenzen.
  • Verwenden Sie Referenzen für das Aliasing (z. B. int &current = 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.


14

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

add_one(&a);

könnte klarer sein als

add_one(a);

da Sie wissen, dass sich der Wert von a höchstwahrscheinlich ändern wird. Auf der anderen Seite jedoch die Signatur der Funktion

void add_one(int* const n);

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

foo(int* const a, int b);

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.


Vielen Dank für die klare Demonstration, wo beide Lösungen Klarheit gewinnen und verlieren. Ich war anfangs im Zeigerlager, aber das macht sehr viel Sinn.
Zach Beavon-Collin

12

Wie andere bereits geantwortet: Verwenden Sie immer Referenzen, es sei denn, die Variable ist NULL/ nullptrist wirklich ein gültiger Zustand.

John Carmacks Standpunkt zu diesem Thema ist ähnlich:

NULL-Zeiger sind das größte Problem in C / C ++, zumindest in unserem Code. Die doppelte Verwendung eines einzelnen Werts als Flag und Adresse verursacht eine unglaubliche Anzahl schwerwiegender Probleme. C ++ - Referenzen sollten nach Möglichkeit Zeigern vorgezogen werden. Während eine Referenz „wirklich“ nur ein Zeiger ist, hat sie den impliziten Vertrag, nicht NULL zu sein. Führen Sie NULL-Prüfungen durch, wenn Zeiger in Referenzen umgewandelt werden. Anschließend können Sie das Problem ignorieren.

http://www.altdevblogaday.com/2011/12/24/static-code-analysis/

Bearbeiten 2012-03-13

User Bret Kuhns bemerkt zu Recht:

Der C ++ 11-Standard wurde fertiggestellt. Ich denke, es ist Zeit in diesem Thread zu erwähnen, dass der meiste Code mit einer Kombination aus Referenzen, shared_ptr und unique_ptr perfekt funktionieren sollte.

Richtig, aber die Frage bleibt auch dann bestehen, wenn Rohzeiger durch intelligente Zeiger ersetzt werden.

Zum Beispiel können beide std::unique_ptrund std::shared_ptrals "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).


1
Der C ++ 11-Standard wurde fertiggestellt. Ich denke, es ist Zeit in diesem Thread zu erwähnen, dass der meiste Code mit einer Kombination von Referenzen shared_ptr, und perfekt funktionieren sollte unique_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.
Bret Kuhns

1
In vielen Fällen sollten intelligente Zeiger nicht weitergegeben werden, sondern auf Nullheit getestet und dann das enthaltene Objekt als Referenz übergeben werden. Das einzige Mal, wenn Sie einen intelligenten Zeiger tatsächlich übergeben sollten, ist, wenn Sie den Besitz eines anderen Objekts übertragen (unique_ptr) oder teilen (shared_ptr).
Luke Worth

@povman: Ich stimme voll und ganz zu: Wenn der Besitz nicht Teil der Schnittstelle ist (und wenn er nicht geändert werden soll, sollte er nicht geändert werden), sollten wir keinen intelligenten Zeiger als Parameter (oder Rückgabewert) übergeben. Die Sache wird etwas komplizierter, wenn der Besitz Teil der Schnittstelle ist. Zum Beispiel die Sutter / Meyers-Debatte darüber, wie ein unique_ptr als Parameter übergeben werden kann: per Kopie (Sutter) oder per R-Wert-Referenz (Meyers)? Ein Antipattern ist darauf angewiesen, einen Zeiger an ein globales shared_ptr weiterzugeben, wobei das Risiko besteht, dass dieser Zeiger ungültig wird (die Lösung besteht darin, den intelligenten Zeiger auf den Stapel zu kopieren)
paercebal

7

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.


5

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, constReferenzen. 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:

bool GetFooArray(array &foo); // my preference
bool GetFooArray(array *foo); // alternative

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.


4

Aus dem Wiki kopiert -

Dies hat zur Folge, dass in vielen Implementierungen das Bearbeiten einer Variablen mit automatischer oder statischer Lebensdauer über eine Referenz, obwohl syntaktisch dem direkten Zugriff ähnlich, versteckte Dereferenzierungsoperationen beinhalten kann, die kostspielig sind. Referenzen sind eine syntaktisch kontroverse Funktion von C ++, da sie die Indirektionsebene eines Bezeichners verdecken. Das heißt, im Gegensatz zu C-Code, bei dem Zeiger normalerweise syntaktisch hervorstechen, ist es in einem großen Block von C ++ - Code möglicherweise nicht sofort offensichtlich, ob das Objekt, auf das zugegriffen wird, als lokale oder globale Variable definiert ist oder ob es sich um eine Referenz (impliziter Zeiger) handelt ein anderer Ort, insbesondere wenn der Code Referenzen und Zeiger mischt. Dieser Aspekt kann das Lesen und Debuggen von schlecht geschriebenem C ++ - Code erschweren (siehe Aliasing).

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.


Ich stimme auch weitgehend zu, komme jedoch zu dem Schluss, dass der Verlust des integrierten Schutzes gegen NULL-Zeiger für rein syntaktische Belange etwas zu kostspielig ist, zumal die Zeigersyntax - obwohl expliziter - ziemlich hässlich ist wie auch immer.
Verbindung

Ich nehme an, der Umstand wäre auch ein wichtiger Faktor. Ich denke, es wäre eine schlechte Idee, Referenzen zu verwenden, wenn die aktuelle Codebasis überwiegend Zeiger verwendet. Wenn Sie erwarten, dass sie Referenzen sind, dann ist die Tatsache, dass sie so implizit sind, vielleicht weniger wichtig.
user606723

3

Zu beachtende Punkte:

  1. Zeiger können sein NULL, Referenzen können nicht sein NULL.

  2. Referenzen sind einfacher zu verwenden, constkönnen als Referenz verwendet werden, wenn wir den Wert nicht ändern möchten und nur eine Referenz in einer Funktion benötigen.

  3. Zeiger verwendet mit einer *Weile Referenzen verwendet mit a &.

  4. Verwenden Sie Zeiger, wenn eine arithmetische Zeigeroperation erforderlich ist.

  5. Sie können Zeiger auf einen Leertyp haben, int a=5; void *p = &a;aber keinen Verweis auf einen Leertyp.

Zeiger gegen Referenz

void fun(int *a)
{
    cout<<a<<'\n'; // address of a = 0x7fff79f83eac
    cout<<*a<<'\n'; // value at a = 5
    cout<<a+1<<'\n'; // address of a increment by 4 bytes(int) = 0x7fff79f83eb0
    cout<<*(a+1)<<'\n'; // value here is by default = 0
}
void fun(int &a)
{
    cout<<a<<'\n'; // reference of original a passed a = 5
}
int a=5;
fun(&a);
fun(a);

Urteil, wann was zu verwenden ist

Zeiger : Für Array-, Linklisten-, Baumimplementierungen und Zeigerarithmetik.

Referenz : In Funktionsparametern und Rückgabetypen.


2

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.

class SimCard
{
    public:
        explicit SimCard(int id):
            m_id(id)
        {
        }

        int getId() const
        {
            return m_id;
        }

    private:
        int m_id;
};

class RefPhone
{
    public:
        explicit RefPhone(const SimCard & card):
            m_card(card)
        {
        }

        int getSimId()
        {
            return m_card.getId();
        }

    private:
        const SimCard & m_card;
};

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.

PtrPhone nullPhone(0);  //this will not happen that easily
SimCard * cardPtr = new SimCard(666);  //evil pointer
delete cardPtr;  //muahaha
PtrPhone uninitPhone(cardPtr);  //this will not happen that easily

Aber dann kommen Provisorien, um deine glückliche Welt zu zerstören.

RefPhone tempPhone(SimCard(666));   //evil temporary
//function referring to destroyed object
tempPhone.getSimId();    //this can happen

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.

  1. Wenn es um die Übergabe von Variablen geht, sieht die Übergabe als Referenz wie die Übergabe als Wert aus, hat jedoch eine Zeigersemantik (verhält sich wie ein Zeiger).
  2. Die Referenz kann nicht direkt auf 0 (null) initialisiert werden.
  3. Referenz (Referenz, nicht referenziertes Objekt) kann nicht geändert werden (entspricht dem Zeiger "* const").
  4. Die const-Referenz kann temporäre Parameter akzeptieren.
  5. Lokale const-Referenzen verlängern die Lebensdauer temporärer Objekte

Unter Berücksichtigung dieser sind meine aktuellen Regeln wie folgt.

  • Verwenden Sie Referenzen für Parameter, die lokal innerhalb eines Funktionsbereichs verwendet werden.
  • Verwenden Sie Zeiger, wenn 0 (null) ein akzeptabler Parameterwert ist oder Sie Parameter zur weiteren Verwendung speichern müssen. Wenn 0 (null) akzeptabel ist, füge ich dem Parameter das Suffix "_n" hinzu, verwende einen geschützten Zeiger (wie QPointer in Qt) oder dokumentiere ihn einfach. Sie können auch intelligente Zeiger verwenden. Sie müssen mit gemeinsam genutzten Zeigern noch vorsichtiger sein als mit normalen Zeigern (andernfalls kann es zu Designleckagen und Verantwortungsproblemen kommen).

3
Das Problem mit Ihrem Beispiel ist nicht, dass Referenzen unsicher sind, sondern dass Sie sich auf etwas außerhalb des Bereichs Ihrer Objektinstanz verlassen, um Ihre privaten Mitglieder am Leben zu erhalten. const SimCard & m_card;ist nur ein schlecht geschriebener Code.
Plamenko

@plamenko Ich fürchte, Sie verstehen den Zweck des Beispiels nicht. Ob const SimCard & m_cardes 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.
Doc

Es gibt zwei Dinge, die mich an Ihrer Antwort stören, weil ich denke, dass sie jemanden irreführen kann, der versucht, mehr über die Angelegenheit zu erfahren. 1. Der Beitrag ist unidirektional und es ist leicht, den Eindruck zu gewinnen, dass Referenzen schlecht sind. Sie haben nur ein einziges Beispiel angegeben, wie Referenzen nicht verwendet werden. 2. In Ihrem Beispiel war Ihnen nicht klar, was daran falsch ist. Ja, vorübergehend wird zerstört, aber es war nicht diese Zeile, die falsch war, es ist die Implementierung der Klasse.
Plamenko

Sie sollten praktisch nie Mitglieder wie haben const SimCard & m_card. Wenn Sie mit temporären Elementen effizient arbeiten möchten, fügen Sie einen explicit RefPhone(const SimCard&& card)Konstruktor hinzu.
Plamenko

@plamenko Wenn du nicht mit einem Grundverständnis lesen kannst, hast du ein größeres Problem als nur durch meinen Beitrag irregeführt zu werden. Ich weiß nicht, wie ich klarer sein könnte. Schauen Sie sich den ersten Satz an. Es gibt ein Problem mit dem Mantra "Referenzen verwenden, wann immer möglich"! Wo in meinem Beitrag haben Sie eine Aussage gefunden, dass Referenzen schlecht sind? Am Ende meines Beitrags haben Sie geschrieben, wo Referenzen verwendet werden sollen. Wie sind Sie zu solchen Schlussfolgerungen gekommen? Dies ist keine direkte Antwort auf die Frage?
Doc

1

Im Folgenden finden Sie einige Richtlinien.

Eine Funktion verwendet übergebene Daten, ohne sie zu ändern:

  1. Wenn das Datenobjekt klein ist, z. B. ein integrierter Datentyp oder eine kleine Struktur, übergeben Sie es als Wert.

  2. 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.

  3. 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.

  4. 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.


0

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

int *pInt = new int; // allocates *pInt on the Heap

0

Ihr richtig geschriebenes Beispiel sollte so aussehen

void add_one(int& n) { n += 1; }
void add_one(int* const n)
{
  if (n)
    *n += 1;
}

Deshalb sind Referenzen möglichst vorzuziehen ...


-1

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.


-2

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.


4
Nee. Für eine Funktion, die einen Zeiger verwendet, muss keine Zeigervariable zugewiesen werden: Sie können eine temporäre Variable übergeben &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.
underscore_d
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.