Restituisce i puntatori intelligenti in base al valore.
Come hai detto, se lo si restituisce per riferimento, non si incrementerà correttamente il conteggio dei riferimenti, il che apre il rischio di eliminare qualcosa in un momento improprio. Solo questo dovrebbe essere una ragione sufficiente per non tornare per riferimento. Le interfacce dovrebbero essere robuste.
La preoccupazione di costo è oggi discutibile grazie a return value optimization (RVO), quindi non si incorre in una sequenza di incremento/decremento incrementale o qualcosa di simile nei moderni compilatori. Quindi il modo migliore per restituire un shared_ptr
è quello di restituire semplicemente il valore:
shared_ptr<T> Foo()
{
return shared_ptr<T>(/* acquire something */);
};
Si tratta di un'occasione RVO morto ovvio per i moderni compilatori C++. So per certo che i compilatori di Visual C++ implementano RVO anche quando tutte le ottimizzazioni sono disattivate. E con la semantica del movimento di C++ 11, questa preoccupazione è ancora meno rilevante. (Ma l'unico modo per essere sicuri è profilare ed esperire.)
Se non sei ancora convinto, Dave Abrahams ha an article che fa un argomento per il ritorno di valore. Riproduco un frammento qui; Consiglio vivamente di leggere l'intero articolo:
Sii sincero: come ti fa sentire il seguente codice?
std::vector<std::string> get_names();
...
std::vector<std::string> const names = get_names();
Francamente, anche se dovrei sapere meglio, mi rende nervoso. In linea di principio, quando ritorna il get_names()
, dobbiamo copiare uno vector
di string
s. Quindi, abbiamo bisogno di copiarlo di nuovo quando inizializziamo names
, e abbiamo bisogno di distruggere la prima copia. Se nel vettore sono presenti N string
s, ciascuna copia potrebbe richiedere un numero di allocazioni di memoria N + 1 e un'intera serie di accessi ai dati cache-non amichevoli> quando i contenuti della stringa vengono copiati.
Invece di confrontarsi con quella specie di ansia, ho spesso ripiegare su pass-by-di riferimento al fine di evitare inutili copie:
get_names(std::vector<std::string>& out_param);
...
std::vector<std::string> names;
get_names(names);
Sfortunatamente, questo approccio è tutt'altro che ideale.
- Il codice è cresciuto del 150%
- Abbiamo dovuto abbandonare
const
-ness perché stiamo mutando nomi.
- Come i programmatori funzionali amano ricordarci, la mutazione rende il codice più complesso da ragionare minando la trasparenza referenziale e il ragionamento equo.
- Non abbiamo più la semantica del valore stretto per i nomi.
Ma è davvero necessario incasinare il nostro codice in questo modo per ottenere efficienza? Fortunatamente, la risposta risulta essere no (e specialmente non se si utilizza C++ 0x).
Non so che direi RVO rende la domanda discutibile dal momento che il rinvio tramite riferimento rende decisamente impossibile il RVO. –
@CrazyEddie: true, questo è uno dei motivi per cui consiglio di restituire l'OP in base al valore. –
La regola RVO, consentita dallo standard, supera le regole sulle relazioni di sincronizzazione/accade-prima, garantite dallo standard? –