Domanda

I riferimenti contenuti in C ++ mi sono sconcertanti. :)

L'idea di base è che sto cercando di restituire un oggetto da una funzione. Mi piacerebbe farlo senza restituire un puntatore (perché poi avrei dovuto delete manualmente), e senza chiamare il costruttore di copia, se possibile (per l'efficienza, naturalmente ha aggiunto: e anche perché mi chiedo se non riesco a evitare di scrivere un costruttore di copia).

Quindi, tutto sommato, qui le opzioni per fare questo che ho trovato:

  • Il tipo di funzione di ritorno può essere sia la classe stessa (MyClass fun() { ... }) o un riferimento alla classe (MyClass& fun() { ... }).
  • La funzione può costruire la variabile nella linea di ritorno (return MyClass(a,b,c);) o restituire una variabile (MyClass x(a,b,c); return x;) esistente.
  • Il codice che riceve la variabile possono anche avere una variabile di entrambi i tipi: (MyClass x = fun(); o MyClass& x = fun();)
  • Il codice che riceve la variabile può creare una nuova variabile al volo (MyClass x = fun();) o assegnare ad una variabile esistente (MyClass x; x = fun();)

E alcuni pensieri su questo:

  • Sembra essere una cattiva idea di avere il tipo di ritorno MyClass& perché risulta sempre nell'essere variabile distrutta prima che venga restituito.
  • Il costruttore di copia sembra solo di essere coinvolti quando torno una variabile esistente. Quando si restituisce una variabile costruito nella linea di ritorno, non viene mai chiamato.
  • Quando ho assegnare il risultato a una variabile esistente, il distruttore anche calci sempre prima viene restituito il valore. Inoltre, nessun costruttore di copia viene chiamato, eppure Variabile target non ricevere i valori dei membri dell'oggetto restituito dalla funzione.

Questi risultati sono così inconsistenti che mi sento totalmente confuso. Allora, che cosa esattamente sta succedendo qui? Come dovrei correttamente costruire e restituire un oggetto da una funzione?

È stato utile?

Soluzione

Il modo migliore per capire la copia in C ++ spesso non è per cercare di produrre un esempio artificiale e strumento - il compilatore è consentito sia rimuovere e aggiungere le chiamate costruttore di copia, più o meno come meglio ritiene opportuno.

Linea di fondo -. Se è necessario restituire un valore, restituire un valore e non preoccuparti di qualsiasi "passive"

Altri suggerimenti

Letture consigliate: Effective C ++ da Scott Meyers. Potete trovare una spiegazione molto buona su questo argomento (e molto di più) in là.

In breve, se si torna per valore, il costruttore di copia e il distruttore saranno coinvolti per default (a meno che il compilatore li ottimizza via - che è ciò che accade in alcuni dei vostri casi).

Se si torna con riferimento (o puntatore) una variabile che è locale (costruito sullo stack), vi invitano guai perché l'oggetto viene distrutto al ritorno, in modo da avere un riferimento penzoloni come un risultato.

Il modo tradizionale per costruire un oggetto in una funzione e ritorno è per valore, come:

MyClass fun() {
    return MyClass(a, b, c);
}

MyClass x = fun();

Se si utilizza questo, non c'è bisogno di preoccuparsi di questioni di proprietà, riferimenti penzoloni ecc E il compilatore molto probabilmente ottimizzare le chiamate copia extra di costruzione / distruzione per voi, quindi non c'è bisogno di preoccuparsi di prestazioni sia.

E 'possibile restituire per riferimento un oggetto costruito da new (cioè sul mucchio) - questo oggetto non viene distrutto al ritorno dalla funzione. Tuttavia, devi distruggere esplicitamente da qualche parte in seguito chiamando delete.

È anche tecnicamente possibile memorizzare un oggetto restituito da valore di riferimento, come:

MyClass& x = fun();

Tuttavia, per quanto ne so non c'è più molto senso fare questo. Soprattutto perché si può facilmente passare questo riferimento ad altre parti del programma che sono al di fuori dell'ambito corrente; tuttavia, l'oggetto di riferimento da x è un oggetto locale che sarà distrutta non appena si lascia l'ambito corrente. Quindi, questo stile può portare a bug fastidiosi.

RVO e NRVO (in una parola i due sta per Return Value Optimization e Named RVO, e sono tecniche di ottimizzazione utilizzate dal compilatore di fare ciò che si sta cercando di raggiungere)

troverete un sacco di soggetti qui su StackOverflow

Se si crea un oggetto come questo:

MyClass foo(a, b, c);

allora sarà in pila nel telaio della funzione. Quando tale funzione termina, il telaio viene estratto dallo stack e tutti gli oggetti che la struttura sia destructed. Non v'è alcun modo per evitare questo.

Quindi, se si vuole restituire un oggetto ad un chiamante, è uniche opzioni sono:

  • di ritorno per valore -. È necessario un costruttore di copia (ma la chiamata al costruttore di copia può essere ottimizzato out)
  • restituire un puntatore e assicurarsi di usare sia puntatori intelligenti a che fare con esso o con cura eliminare da soli quando fatto con esso.

Il tentativo di costruire un oggetto locale e poi restituire un riferimento a tale memoria locale di un contesto di chiamata non è coerente - un ambito chiamata non può accedere alla memoria che è locale chiamato portata. Che la memoria locale è valido solo per la durata della funzione che possiede - o, in altro modo, mentre l'esecuzione rimane in tale ambito. È necessario capire questo per programmare in C ++.

Circa l'unica volta che ha senso per restituire un riferimento è se si sta restituendo un riferimento a un oggetto preesistente. Per un esempio ovvio, quasi ogni funzione membro iostream restituisce un riferimento al iostream. L'iostream stessa esiste prima di qualsiasi delle funzioni membro è chiamato, e continua ad esistere dopo che sono stati chiamati.

Lo standard permette di "copia elisione", il che significa che il costruttore di copia non ha bisogno di essere chiamato quando si torna un oggetto. Questo è disponibile in due forme:. Nome Return Value Optimization (NRVO) e anonimo Return Value Optimization (di solito solo RVO)

Da quello che stai dicendo, il compilatore implementa RVO ma non NRVO - che significa che è probabilmente un compilatore po 'più vecchio. La maggior parte dei compilatori attuali implementano entrambi. Il dtor non-abbinato in questo caso significa che è probabilmente qualcosa come gcc 3.4 o giù di lì - anche se non mi ricordo la versione di sicuro, c'era un intorno che poi ha avuto un bug come questo. Naturalmente, è anche possibile che il vostro strumentazione non è giusto, quindi un ctor che non hai strumento è in uso, e un dtor corrispondenza sia invocata per tale oggetto.

Alla fine, sei bloccato con un semplice fatto però: se è necessario restituire un oggetto, è necessario restituire un oggetto. In particolare, un riferimento può fornire l'accesso a una (versione eventualmente modificata) un oggetto esistente - ma quell'oggetto doveva essere costruito ad un certo punto pure. Se è possibile modificare un oggetto esistente senza causare un problema, va bene e bene, andare avanti e farlo. Se avete bisogno di un nuovo oggetto diverso e separato da quelli che già avete, andare avanti e fare che - pre-creazione dell'oggetto e passando in un riferimento ad esso può rendere il ritorno in sé più veloce, ma non salvare qualsiasi tempo complessivo. Creare l'oggetto ha circa lo stesso costo se fatto all'interno o all'esterno della funzione. Ogni compilatore ragionevolmente moderna includerà RVO, in modo da non pagare alcun costo aggiuntivo per la creazione di esso nella funzione, per poi tornare - il compilatore sarà solo di automatizzare l'allocazione dello spazio per l'oggetto in cui si sta per essere restituito, e hanno la funzione di costruirlo "in luogo", dove sarà ancora accessibile dopo la funzione ritorna.

Fondamentalmente, restituendo un riferimento senso solo se l'oggetto esiste ancora dopo aver lasciato il metodo. Il compilatore avviserà se restituite un riferimento a qualcosa che viene distrutta.

Tornando un riferimento piuttosto che un oggetto dal valore salva copiando l'oggetto che potrebbe essere significativo.

I riferimenti sono più sicuri di puntatori perché hanno diversi symantics, ma dietro le quinte che sono puntatori.

Una possibile soluzione, a seconda del caso d'uso, è quello di default-costruire l'oggetto al di fuori della funzione, prendere in un riferimento ad esso, e inizializzare l'oggetto di riferimento all'interno della funzione, in questo modo :

void initFoo(Foo& foo) 
{
  foo.setN(3);
  foo.setBar("bar");
  // ... etc ...
}

int main() 
{
  Foo foo;
  initFoo(foo);

  return 0;
}

Ora, questo ovviamente non funziona se non è possibile (o non ha senso) di default-costruire un oggetto Foo e poi inizializzare in un secondo momento. Se questo è il caso, allora la vostra unica vera opzione per evitare la copia-costruzione è di restituire un puntatore ad un oggetto heap allocata.

Ma allora riflettere sul perché si sta cercando di evitare il copia-costruzione, in primo luogo. È la "spesa" di costruzione copia realmente interessano il vostro programma, o si tratta di un caso di ottimizzazione prematura?

Si sono stucked sia con:

1) restituisce un puntatore

MyClass * func () {   // alcuni stuf   ritorno new MyClass (a, b, c); }

2) restituisce una copia dell'oggetto MyClass func () {   restituire MyClass (a, b, c); }

Tornando un riferimento non è valida perché l'oggetto deve essere distrutto dopo l'uscita dal campo di applicazione func, tranne se la funzione è un membro della classe e il riferimento è da una variabile che è membro della classe.

Non è una risposta diretta, ma un suggerimento praticabile: Si potrebbe anche restituire un puntatore, avvolto in un auto_ptr o smart_ptr. Allora sarete in controllo di ciò che costruttori e distruttori vengono chiamati e quando.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top