Devo impostare valori usando il reso o dando un riferimento?

0

Supponiamo di avere un osservatore della posizione del mouse, che dovrebbe consentire all'utente di estrarre la posizione del mouse. Se la posizione del mouse è impostata nel metodo e restituita:

Mouse_Position Mouse_Watcher::get_mouse_position( void ) const
{
   Mouse_Position return_val = Mouse_Position( this->mp );

   // memory stuff so the value can escape this scope and not cause segmentation faults

   return return_val;
}

Mouse_Position mp = Mouse_Position();
mp = mouse_man::get_mouse_position();

o il modo leggermente più semplice che ho trovato:

void Mouse_Watcher::sync_mouse_position( Mouse_Position & destination ) const
{
   // the class handles copying
   // if the stuff is primitive and uses this syncing method,
   //   seems like a ton of avoided memory mishaps
   destination.sync_x( this->mp );
   destination.sync_y( this->mp );
}

Mouse_Position mp = Mouse_Position();
mouse_man::sync_mouse_position( mp );

// at this point mp has position values

Sono stato abituato a impostare valori usando il ritorno, ma non mi piace inutilmente inclusa la gestione della memoria se posso aiutarlo. Dovrei tornare a utilizzare i resi per stare al passo con gli standard?

    
posta user2738698 14.05.2015 - 21:42
fonte

2 risposte

3

Non sorprende che ciò dipenda dallo scopo della funzione e dalla natura del tipo che sta recuperando / estraendo. Ma di solito è facile fare una scelta in base alle proprietà del tipo.

1) Il valore è un elemento primitivo come int o char

Quindi la scelta è effettivamente tra:

int x = getInt();

e

int x;
setInt(x);

Preferisco strongmente restituire il valore, perché:

  • Sembra solo più naturale / normale / intuitivo / leggibile / ecc.
  • Ora che la funzione accetta un argomento, è possibile che il suo comportamento dipende dal valore corrente di tale argomento. Semanticamente, il valore corrente di x è completamente irrilevante rispetto a ciò che fa questa funzione, quindi perché dare alla funzione l'accesso ad esso?
  • Copiare e assegnare int s costa praticamente nulla. Le probabilità sono entrambe le versioni ottimizzate per le stesse istruzioni comunque.

2) Il valore è una struct / class "banale" (ad esempio, nessun dato privato, metodi o gestione della memoria)

Presumo che la tua classe Mouse_Position rientra in questa categoria. Questo caso è fondamentalmente identico al n. 1, perché una classe come questa si comporta più o meno come una primitiva.

Più formalmente, quando dico che questa classe è come un valore primitivo, sto dicendo che questa classe ha "semantica del valore". Ciò significa un sacco di cose, ad esempio se due oggetti di quella classe hanno gli stessi membri dei dati, sono completamente intercambiabili a tutti gli effetti. Questa è una proprietà molto bella da avere quando si tratta di ragionare sul proprio codice.

3) Il valore ha metodi / dati privati / gestione della memoria, ma implementa RAII e ancora tecnicamente ha semantica del valore

È qui che vanno le classi del contenitore STL. Un vettore std :: contiene sicuramente dati privati, tra cui un puntatore alla memoria che deve gestire manualmente, ma i suoi costruttori, i distruttori e le operazioni di copia sono implementati in modo tale da comportarsi ancora come un valore primitivo in molti modi. In particolare, due vettori che si confrontano uguali sono ancora intercambiabili; non importa in che regione della memoria si trovano i loro valori.

In questo caso, se si restituisce per valore o si prende un parametro di riferimento si riduce in genere all'efficienza. In C ++ normalmente non si desidera copiare classi come questa se non è necessario, perché ciò implica la copia di tutti i dati privati o la memoria che viene gestita (il che potrebbe comportare l'allocazione di più memoria). Tuttavia, spostare la semantica nel moderno C ++ fa in modo che la restituzione di uno std :: vector non esegua necessariamente più una copia, quindi semplicemente restituirla per valore sta diventando più accettabile (non cercherò di spiegare le condizioni esatte in cui le copie fanno / non succede prima / dopo C ++ 11, è troppo complicato).

4) Il valore non ha traccia di semantica del valore e la conservazione della sua identità è importante

Di solito sono classi che rappresentano dipendenze esterne come file, database, connessioni di rete, dispositivi periferici, contesti di rendering e così via. Spesso non ha senso copiare questi oggetti (non è possibile dare al computer più tastiere copiando una classe Keyboard ), quindi le funzioni che operano su di essi devono fondamentalmente operare su puntatori / riferimenti ad essi come una semplice questione di correttezza. Ovviamente qui la decisione è presa per te.

Risposta breve: non utilizzare puntatori / riferimenti quando sono completamente inutili. La semplice copia / restituzione dei valori spesso porta a un codice più intuitivo e con un comportamento migliore.

    
risposta data 14.05.2015 - 22:49
fonte
1

Nella maggior parte dei casi, dovresti preferire effettivamente restituire le cose dalle funzioni. Il passaggio in un riferimento non const per mantenere lo stato del valore viene in genere definito come un parametro out o un parametro in-out se si utilizza entrambi lo stato esistente e quindi si modifica.

Ci sono un paio di svantaggi dei parametri di riferimento non const:

1) Sembra imbarazzante, ed è meno chiaro. Non si sa se si tratta di un parametro out o di un parametro in / out senza documentazione. Forse è un parametro normale ma qualcuno ha dimenticato const .

2) Devi costruire tu stesso un valore e poi passarlo. Contrasto con il valore restituito, il tuo primo esempio è scritto meglio come

Mouse_Position mp = mouse_man::get_mouse_position();

Non creo mai un oggetto inutile e riduco il rumore nel mio codice.

Ci sono solo un paio di motivi per cui vorrei prendere in considerazione l'utilizzo del modello che stai suggerendo; Li elencherò dal meno al più tecnico.

1) L'oggetto è in realtà un parametro in / out. Ciò significa che si desidera utilizzare lo stato esistente dell'oggetto. I parametri di ingresso / uscita sono spesso meglio evitati, ma a volte sono una buona scelta. Questo non è il caso qui.

2) L'oggetto che devi restituire non è né copiabile né mobile (o stai usando c ++ 03, e non è copiabile, ma non lo considererò). Questo è estremamente raro.

3) Ragioni di prestazione. Questo è un problema abbastanza coinvolto. La regola generale qui è che il compilatore è più intelligente di quanto si pensi. Ad esempio, se il tuo tipo di dati è grande, potresti obiettare che il ritorno da una funzione implica una copia, che è costosa, mentre il passaggio di un riferimento e la sua modifica potrebbero essere in qualche modo più economici. In pratica, a causa di cose meravigliose come il Return Value Optimization ( link ) questo costo viene eliminato nella maggior parte delle situazioni. Potrei parlare molto di più su questo, ma è un argomento complicato e non sembra essere pertinente qui.

In breve, con la stragrande maggioranza dei tipi e senza considerazioni sulle prestazioni estreme, dovresti semplicemente restituire il tipo in base al valore, a meno che non ti interessi effettivamente del suo stato precedente.

    
risposta data 14.05.2015 - 23:32
fonte

Leggi altre domande sui tag