Motivo per passare un puntatore per riferimento in C ++?


97

In quali circostanze vorresti usare codice di questa natura in c ++?

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

void fii() {
  type *choochoo;
  ...
  foo(choochoo);
}

se hai bisogno di restituire un puntatore - meglio usare un valore di ritorno
littleadv

6
Puoi spiegare perché? Questo elogio non è molto utile. La mia domanda è abbastanza legittima. Questo è attualmente utilizzato nel codice di produzione. Non capisco appieno perché.
Matthew Hoggan

1
David lo riassume abbastanza bene. Il puntatore stesso è in fase di modifica.
chris

2
Passo in questo modo se chiamerò l'operatore "Nuovo" nella funzione.
NDEthos

Risposte:


142

Si desidera passare un puntatore per riferimento se è necessario modificare il puntatore anziché l'oggetto a cui punta.

Questo è simile al motivo per cui vengono utilizzati i doppi puntatori; l'utilizzo di un riferimento a un puntatore è leggermente più sicuro rispetto all'utilizzo dei puntatori.


14
Così simile a un puntatore di un puntatore, tranne un livello in meno di riferimento indiretto per la semantica di riferimento?

Vorrei aggiungere che a volte vuoi anche restituire un puntatore per riferimento. Ad esempio, se si dispone di una classe che contiene i dati in un array allocato dinamicamente, ma si desidera fornire al client un accesso (non costante) a questi dati. Allo stesso tempo, non si desidera che il client sia in grado di manipolare la memoria tramite il puntatore.

2
@ William puoi approfondire questo? Sembra interessante. Significa che l'utente di quella classe potrebbe ottenere un puntatore al buffer di dati interno e leggere / scrivere su di esso, ma non può - per esempio - liberare quel buffer usando deleteo riassociare quel puntatore a qualche altro posto nella memoria? O ho sbagliato?
BarbaraKwarc

2
@BarbaraKwarc Sure. Supponi di avere una semplice implementazione di un array dinamico. Naturalmente sovraccaricherai l' []operatore. Una possibile (e in effetti naturale) firma per questo sarebbe const T &operator[](size_t index) const. Ma potresti anche avere T &operator[](size_t index). Potresti averli entrambi allo stesso tempo. Quest'ultimo ti permetterebbe di fare cose come myArray[jj] = 42. Allo stesso tempo, non stai fornendo un puntatore ai tuoi dati, quindi il chiamante non può interferire con la memoria (ad esempio, cancellarla accidentalmente).

Oh. Pensavo stessi parlando della restituzione di un riferimento a un puntatore (la tua esatta formulazione: "restituisci un puntatore per riferimento", perché questo era ciò di cui OP stava chiedendo: passare i puntatori per riferimenti, non solo semplici vecchi riferimenti) come un modo stravagante di dando accesso in lettura / scrittura al buffer senza permettere di manipolare il buffer stesso (es. liberandolo). Restituire un semplice vecchio riferimento è una cosa diversa e lo so.
BarbaraKwarc

65

Il 50% dei programmatori C ++ preferisce impostare i propri puntatori su null dopo un'eliminazione:

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

Senza il riferimento, cambieresti solo una copia locale del puntatore, senza influire sul chiamante.


19
Mi piace il nome; )
4pie0

2
Scambierò sembrando stupido per aver trovato la risposta: perché si chiama eliminazione idiota? Cosa mi sto perdendo?
Sammaron

1
@Sammaron Se guardi la cronologia, il modello della funzione si chiamava paranoid_delete, ma Puppy lo ha rinominato moronic_delete. Probabilmente appartiene al restante 50%;) Ad ogni modo, la risposta breve è che i puntatori di impostazioni a null after non deletesono quasi mai utili (perché dovrebbero comunque uscire dall'ambito) e spesso ostacola il rilevamento di bug "use after free".
fredoverflow

@fredoverflow Capisco la tua risposta, ma @Puppy sembra pensare che deletesia stupido in generale. Puppy, ho fatto un po 'di googling, non riesco a capire perché eliminare sia completamente inutile (forse sono anche un pessimo googler;)). Puoi spiegare un po 'di più o fornire un link?
Sammaron

@ Sammaron, C ++ ha "puntatori intelligenti" ora che incapsulano puntatori regolari e chiamano automaticamente "cancella" quando escono dall'ambito. I puntatori intelligenti sono di gran lunga preferiti rispetto ai puntatori stupidi in stile C perché eliminano completamente questo problema.
tjwrona1992

14

La risposta di David è corretta, ma se è ancora un po 'astratta, ecco due esempi:

  1. Potresti voler azzerare tutti i puntatori liberati per rilevare prima i problemi di memoria. Stile C faresti:

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

    In C ++ per fare lo stesso, potresti fare:

    template<class T> void freeAndZero(T* &ptr)
    {
        delete ptr;
        ptr = 0;
    }
    
    int* ptr = new int;
    
    ...
    
    freeAndZero(ptr);
  2. Quando si ha a che fare con elenchi collegati, spesso rappresentati semplicemente come puntatori a un nodo successivo:

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

    In questo caso, quando si inserisce nella lista vuota è necessario necessariamente modificare il puntatore in entrata perché il risultato non è più il NULLpuntatore. Questo è il caso in cui modifichi un puntatore esterno da una funzione, quindi avrebbe un riferimento al puntatore nella sua firma:

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

C'è un esempio in questa domanda .


8

Ho dovuto usare un codice come questo per fornire funzioni per allocare memoria a un puntatore passato e restituirne le dimensioni perché la mia azienda "oggetto" per me usando l'STL

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

Non è carino, ma il puntatore deve essere passato per riferimento (o utilizzare il doppio puntatore). In caso contrario, la memoria viene allocata a una copia locale del puntatore se viene passata per valore che si traduce in una perdita di memoria.


2

Un esempio è quando si scrive una funzione parser e le si passa un puntatore sorgente da cui leggere, se si suppone che la funzione spinga quel puntatore in avanti dietro l'ultimo carattere che è stato correttamente riconosciuto dal parser. L'uso di un riferimento a un puntatore rende chiaro quindi che la funzione sposterà il puntatore originale per aggiornare la sua posizione.

In generale, si utilizzano riferimenti ai puntatori se si desidera passare un puntatore a una funzione e lasciare che sposti il puntatore originale in un'altra posizione invece di spostarne solo una copia senza influire sull'originale.


Perché il voto negativo? C'è qualcosa che non va in quello che ho scritto? Ti va di spiegare? : P
BarbaraKwarc

0

Un'altra situazione in cui potrebbe essere necessario questo è se si dispone di una raccolta di puntatori stl e si desidera modificarli utilizzando l'algoritmo stl. Esempio di 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();
  } 

};

Altrimenti, se usi la firma changeObject (Object * item) hai una copia del puntatore, non quella originale.


questo è più un "oggetto di sostituzione" che un "oggetto di modifica" - c'è una differenza
serup
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.