Grund, einen Zeiger in C ++ als Referenz zu übergeben?

97

Unter welchen Umständen möchten Sie Code dieser Art in c ++ verwenden?

void foo(type *&in) {...}

void fii() {
  type *choochoo;
  ...
  foo(choochoo);
}
Matthew Hoggan
quelle
Wenn Sie einen Zeiger zurückgeben müssen - verwenden Sie besser einen Rückgabewert
littleadv
6
Können Sie erklären, warum? Dieses Lob ist nicht sehr hilfreich. Meine Frage ist ziemlich legitim. Dies wird derzeit im Produktionscode verwendet. Ich verstehe einfach nicht ganz warum.
Matthew Hoggan
1
David fasst es ganz gut zusammen. Der Zeiger selbst wird geändert.
Chris
2
Ich gehe diesen Weg, wenn ich den Operator "Neu" in der Funktion aufrufe.
NDEthos

Antworten:

142

Sie möchten einen Zeiger als Referenz übergeben, wenn Sie den Zeiger anstelle des Objekts ändern müssen, auf das der Zeiger zeigt.

Dies ähnelt dem Grund, warum Doppelzeiger verwendet werden. Die Verwendung eines Verweises auf einen Zeiger ist etwas sicherer als die Verwendung von Zeigern.

David Z.
quelle
14
So ähnlich wie ein Zeiger eines Zeigers, mit Ausnahme einer Indirektionsebene weniger für die Referenz-Semantik?
Ich möchte hinzufügen, dass Sie manchmal auch einen Zeiger als Referenz zurückgeben möchten. Wenn Sie beispielsweise eine Klasse haben, die Daten in einem dynamisch zugewiesenen Array enthält, dem Client jedoch einen (nicht konstanten) Zugriff auf diese Daten gewähren möchten. Gleichzeitig möchten Sie nicht, dass der Client den Speicher über den Zeiger bearbeiten kann.
2
@ William kannst du das näher erläutern? Das klingt interessant. Bedeutet dies, dass der Benutzer dieser Klasse einen Zeiger auf den internen Datenpuffer erhalten und in diesen lesen / schreiben kann, diesen Puffer jedoch nicht deletefreigeben kann, indem er diesen Zeiger an einer anderen Stelle im Speicher verwendet oder erneut bindet? Oder habe ich es falsch verstanden?
BarbaraKwarc
2
@BarbaraKwarc Sicher. Angenommen, Sie haben eine einfache Implementierung eines dynamischen Arrays. Natürlich überlasten Sie den []Bediener. Eine mögliche (und tatsächlich natürliche) Signatur hierfür wäre const T &operator[](size_t index) const. Aber du könntest es auch haben T &operator[](size_t index). Sie könnten beide gleichzeitig haben. Letzteres würde Sie Dinge wie tun lassen myArray[jj] = 42. Gleichzeitig geben Sie keinen Zeiger auf Ihre Daten an, sodass der Anrufer nicht mit dem Speicher herumspielen kann (z. B. versehentlich löschen).
Oh. Ich dachte, Sie sprachen über die Rückgabe eines Verweises auf einen Zeiger (Ihr genauer Wortlaut: "Rückgabe eines Zeigers durch Verweis", weil OP danach fragte: Übergeben von Zeigern durch Verweise, nicht nur über alte Verweise) als eine ausgefallene Methode Lese- / Schreibzugriff auf den Puffer gewähren, ohne den Puffer selbst manipulieren zu dürfen (z. B. freigeben). Eine einfache alte Referenz zurückzugeben ist etwas anderes und das weiß ich.
BarbaraKwarc
65

50% der C ++ - Programmierer setzen ihre Zeiger nach dem Löschen gerne auf null:

template<typename T>    
void moronic_delete(T*& p)
{
    delete p;
    p = nullptr;
}

Ohne die Referenz würden Sie nur eine lokale Kopie des Zeigers ändern, ohne den Anrufer zu beeinflussen.

Fredoverflow
quelle
19
Ich mag den Namen; )
4pie0
2
Ich werde dumm klingen, um die Antwort herauszufinden: Warum heißt es schwachsinniges Löschen? Was vermisse ich?
Sammaron
1
@Sammaron Wenn Sie sich den Verlauf ansehen, wurde die Funktionsvorlage früher aufgerufen paranoid_delete, aber Puppy hat sie umbenannt moronic_delete. Er gehört wahrscheinlich zu den anderen 50%;) Wie auch immer, die kurze Antwort ist, dass Einstellungszeiger auf null nach deletefast nie nützlich sind (weil sie sowieso außerhalb des Gültigkeitsbereichs liegen sollten) und oft die Erkennung von "Verwendung nach freien" Fehlern behindern.
Fredoverflow
@fredoverflow Ich verstehe Ihre Antwort, aber @Puppy scheint deleteim Allgemeinen dumm zu sein. Welpe, ich habe ein bisschen gegoogelt, ich kann nicht verstehen, warum Löschen völlig nutzlos ist (vielleicht bin ich auch ein schrecklicher Googler;)). Können Sie etwas mehr erklären oder einen Link bereitstellen?
Sammaron
@Sammaron, C ++ verfügt jetzt über "intelligente Zeiger", die reguläre Zeiger kapseln und automatisch "Löschen" für Sie aufrufen, wenn sie den Gültigkeitsbereich verlassen. Intelligente Zeiger werden dummen Zeigern im C-Stil weit vorgezogen, da sie dieses Problem vollständig beseitigen.
tjwrona1992
14

Davids Antwort ist richtig, aber wenn sie noch ein wenig abstrakt ist, hier zwei Beispiele:

  1. Möglicherweise möchten Sie alle freigegebenen Zeiger auf Null setzen, um Speicherprobleme früher zu erkennen. C-Stil würden Sie tun:

    void freeAndZero(void** ptr)
    {
        free(*ptr);
        *ptr = 0;
    }
    
    void* ptr = malloc(...);
    
    ...
    
    freeAndZero(&ptr);
    

    In C ++ können Sie Folgendes tun, um dasselbe zu tun:

    template<class T> void freeAndZero(T* &ptr)
    {
        delete ptr;
        ptr = 0;
    }
    
    int* ptr = new int;
    
    ...
    
    freeAndZero(ptr);
    
  2. Beim Umgang mit verknüpften Listen - oft einfach als Zeiger auf einen nächsten Knoten dargestellt:

    struct Node
    {
        value_t value;
        Node* next;
    };
    

    In diesem Fall müssen Sie beim Einfügen in die leere Liste unbedingt den eingehenden Zeiger ändern, da das Ergebnis nicht NULLmehr der Zeiger ist. Dies ist ein Fall, in dem Sie einen externen Zeiger von einer Funktion aus ändern, sodass er in seiner Signatur einen Verweis auf den Zeiger enthält:

    void insert(Node* &list)
    {
        ...
        if(!list) list = new Node(...);
        ...
    }
    

In dieser Frage gibt es ein Beispiel .

Antoine
quelle
8

Ich musste Code wie diesen verwenden, um Funktionen bereitzustellen, um einem übergebenen Zeiger Speicher zuzuweisen und seine Größe zurückzugeben, weil mein Unternehmen mir mithilfe der STL "Objekt" gibt

 int iSizeOfArray(int* &piArray) {
    piArray = new int[iNumberOfElements];
    ...
    return iNumberOfElements;
 }

Es ist nicht schön, aber der Zeiger muss als Referenz übergeben werden (oder Doppelzeiger verwenden). Wenn nicht, wird Speicher einer lokalen Kopie des Zeigers zugewiesen, wenn er als Wert übergeben wird, was zu einem Speicherverlust führt.

Mathematiker 1975
quelle
2

Ein Beispiel ist, wenn Sie eine Parser-Funktion schreiben und ihr einen Quellzeiger zum Lesen übergeben, wenn die Funktion diesen Zeiger hinter das letzte Zeichen verschieben soll, das vom Parser korrekt erkannt wurde. Die Verwendung eines Verweises auf einen Zeiger macht dann deutlich, dass die Funktion den ursprünglichen Zeiger bewegt, um seine Position zu aktualisieren.

Im Allgemeinen verwenden Sie Verweise auf Zeiger, wenn Sie einen Zeiger auf eine Funktion übergeben und diesen ursprünglichen Zeiger an eine andere Position verschieben möchten, anstatt nur eine Kopie davon zu verschieben, ohne das Original zu beeinflussen.

BarbaraKwarc
quelle
Warum das Downvote? Stimmt etwas mit dem, was ich geschrieben habe, nicht? Möchtest du das erklären? : P
BarbaraKwarc
0

Eine andere Situation, in der Sie dies möglicherweise benötigen, besteht darin, dass Sie über eine stl-Sammlung von Zeigern verfügen und diese mithilfe des stl-Algorithmus ändern möchten. Beispiel für for_each in c ++ 98.

struct Storage {
  typedef std::list<Object*> ObjectList;
  ObjectList objects;

  void change() {
    typedef void (*ChangeFunctionType)(Object*&);
    std::for_each<ObjectList::iterator, ChangeFunctionType>
                 (objects.begin(), objects.end(), &Storage::changeObject);
  }

  static void changeObject(Object*& item) {
    delete item;
    item = 0;
    if (someCondition) item = new Object();
  } 

};

Andernfalls haben Sie bei Verwendung der Signatur changeObject (Object * item) eine Kopie des Zeigers und keine Originalkopie.

Neun
quelle
Dies ist eher ein "Objekt ersetzen" als ein "Objekt ändern" - es gibt einen Unterschied
Serup