reference reference - La pratica di restituire una variabile di riferimento C++, il male?




vs pointer (14)

Questo è un po 'soggettivo penso; Non sono sicuro che il parere sarà unanime (ho visto molti frammenti di codice in cui vengono restituiti i riferimenti).

Secondo un commento a questa domanda, ho appena chiesto, per quanto riguarda i riferimenti di inizializzazione , restituire un riferimento può essere malvagio perché, [come ho capito] rende più facile perdere l'eliminazione, che può portare a perdite di memoria.

Questo mi preoccupa, come ho seguito degli esempi (a meno che non mi stia immaginando le cose) e l'ho fatto in un discreto numero di posti ... Ho frainteso? È cattivo? Se è così, quanto male?

Sento che grazie al mio bagaglio di riferimenti e puntatori, unito al fatto che sono nuovo al C ++ e alla totale confusione su cosa usare quando, le mie applicazioni devono essere fatiche della memoria ...

Inoltre, capisco che l'utilizzo di puntatori intelligenti / condivisi è generalmente accettato come il modo migliore per evitare perdite di memoria.


Answers

No. No, no, mille volte no.

Qual è il male sta facendo riferimento ad un oggetto allocato dinamicamente e perdendo il puntatore originale. Quando si new un new oggetto, si assume l'obbligo di disporre di delete garantita.

Ma date un'occhiata, ad esempio, operator<< : che deve restituire un riferimento, o

cout << "foo" << "bar" << "bletch" << endl ;

non funzionerà


È necessario restituire un riferimento a un oggetto esistente che non sta andando via immediatamente e dove non si intende alcun trasferimento di proprietà.

Non restituire mai un riferimento a una variabile locale o qualcosa di simile, perché non sarà lì per essere referenziato.

È possibile restituire un riferimento a qualcosa di indipendente dalla funzione, che non si aspetta che la funzione chiamante si assuma la responsabilità dell'eliminazione. Questo è il caso della tipica funzione operator[] .

Se stai creando qualcosa, dovresti restituire un valore o un puntatore (normale o intelligente). È possibile restituire un valore liberamente, poiché sta entrando in una variabile o espressione nella funzione chiamante. Non restituire mai un puntatore a una variabile locale, poiché andrà via.



"restituire un riferimento è male perché, semplicemente [come ho capito] rende più facile perdere l'eliminazione"

Non vero. Restituire un riferimento non implica la semantica della proprietà. Cioè, solo perché lo fai:

Value& v = thing->getTheValue();

... non significa che ora possiedi la memoria a cui fa riferimento v;

Tuttavia, questo è un codice orribile:

int& getTheValue()
{
   return *new int;
}

Se stai facendo qualcosa del genere perché "non hai bisogno di un puntatore su quell'istanza" allora: 1) basta dereferenziare il puntatore se hai bisogno di un riferimento, e 2) alla fine avrai bisogno del puntatore, perché devi abbinare un nuovo con una cancellazione, e hai bisogno di un puntatore per chiamare eliminare.


La cosa migliore è creare un oggetto e passarlo come parametro riferimento / puntatore a una funzione che assegna questa variabile.

Allocare l'oggetto in funzione e restituirlo come riferimento o puntatore (il puntatore è più sicuro comunque) è una cattiva idea a causa della liberazione della memoria alla fine del blocco funzione.


Penso che usando il riferimento come il valore di ritorno della funzione sia molto più diretto rispetto all'utilizzo del puntatore come valore di ritorno della funzione. In secondo luogo, sarebbe sempre sicuro utilizzare la variabile statica a cui si riferisce il valore di ritorno.


In generale, restituire un riferimento è perfettamente normale e accade sempre.

Se intendi:

int& getInt() {
    int i;
    return i;  // DON'T DO THIS.
}

Questo è ogni tipo di male. Lo stack-assegnato andrò via e non ti riferisci a nulla. Questo è anche il male:

int& getInt() {
    int* i = new int;
    return *i;  // DON'T DO THIS.
}

Perché ora il cliente deve alla fine fare lo strano:

int& myInt = getInt(); // note the &, we cannot lose this reference!
delete &myInt;         // must delete...totally weird and  evil

int oops = getInt(); 
delete &oops; // undefined behavior, we're wrongly deleting a copy, not the original

Si noti che i riferimenti rvalue sono ancora solo riferimenti, quindi tutte le applicazioni malvagie rimangono le stesse.

Se si desidera allocare qualcosa che vada oltre lo scopo della funzione, utilizzare un puntatore intelligente (o, in generale, un contenitore):

std::unique_ptr<int> getInt() {
    return std::make_unique<int>(0);
}

E ora il client memorizza un puntatore intelligente:

std::unique_ptr<int> x = getInt();

I riferimenti sono anche validi per accedere a cose in cui sai che la durata è mantenuta aperta a un livello superiore, ad esempio:

struct immutableint {
    immutableint(int i) : i_(i) {}

    const int& get() const { return i_; }
private:
    int i_;
};

Qui sappiamo che è ok per restituire un riferimento a i_ perché qualsiasi cosa ci chiami gestisce la durata dell'istanza della classe, quindi i_ vivrò almeno così a lungo.

E, naturalmente, non c'è niente di sbagliato in:

int getInt() {
   return 0;
}

Se la durata deve essere lasciata al chiamante e stai solo calcolando il valore.

Riepilogo: è possibile restituire un riferimento se la durata dell'oggetto non termina dopo la chiamata.


Ci sono due casi:

  • const reference - buona idea, a volte, soprattutto per oggetti pesanti o classi proxy, ottimizzazione del compilatore

  • riferimento non const - l'idea scomoda, a volte, rompe gli incapsulamenti

Entrambi condividono lo stesso problema: possono potenzialmente indicare oggetti distrutti ...

Consiglierei l'uso di puntatori intelligenti per molte situazioni in cui è necessario restituire un riferimento / puntatore.

Inoltre, tieni presente quanto segue:

C'è una regola formale - lo standard C ++ (sezione 13.3.3.1.4 se sei interessato) afferma che un temporaneo può essere associato solo a un riferimento const - se si tenta di usare un riferimento non const il compilatore deve contrassegnarlo come un errore.


Non è male. Come molte cose in C ++, è buono se usato correttamente, ma ci sono molte insidie ​​che dovresti sapere quando lo usi (come restituire un riferimento a una variabile locale).

Ci sono cose buone che possono essere raggiunte con esso (come map [nome] = "ciao mondo")


La funzione come lvalue (ovvero il ritorno di riferimenti non const) dovrebbe essere rimossa da C ++. È terribilmente poco intuitivo. Scott Meyers voleva un min () con questo comportamento.

min(a,b) = 0;  // What???

che non è davvero un miglioramento

setmin (a, b, 0);

Quest'ultimo ha anche più senso.

Mi rendo conto che la funzione come lvalue è importante per i flussi di stile C ++, ma vale la pena sottolineare che i flussi di stile C ++ sono terribili. Non sono l'unico a pensarlo ... perché ricordo che Alexandrescu aveva un ampio articolo su come fare meglio, e credo che boost abbia anche provato a creare un metodo I / O sicuro migliore.


Non solo non è malvagio, a volte è essenziale. Ad esempio, sarebbe impossibile implementare l'operatore [] di std :: vector senza utilizzare un valore di riferimento.


    Class Set {
    int *ptr;
    int size;

    public: 
    Set(){
     size =0;
         }

     Set(int size) {
      this->size = size;
      ptr = new int [size];
     }

    int& getPtr(int i) {
     return ptr[i];  // bad practice 
     }
  };

la funzione getPtr può accedere alla memoria dinamica dopo la cancellazione o anche un oggetto nullo. Che può causare eccezioni di accesso non valido. Invece getter e setter dovrebbero essere implementati e le dimensioni devono essere verificate prima di tornare.


Informazioni su codice orribile:

int& getTheValue()
{
   return *new int;
}

Quindi, in effetti, il puntatore della memoria ha perso dopo il ritorno. Ma se usi shared_ptr in questo modo:

int& getTheValue()
{
   std::shared_ptr<int> p(new int);
   return *p->get();
}

La memoria non viene persa dopo il ritorno e sarà liberata dopo l'assegnazione.


C'è un piccolo trucco per passare un oggetto per riferimento, anche se la lingua non lo rende possibile. Funziona anche in Java, è la lista con un oggetto. ;-)

class PassByReference:
    def __init__(self, name):
        self.name = name

def changeRef(ref):
    ref[0] = PassByReference('Michael')

obj = PassByReference('Peter')
print obj.name

p = [obj] # A pointer to obj! ;-)
changeRef(p)

print p[0].name # p->name

È un brutto trucco, ma funziona. ;-P





c++ reference