Non sono d'accordo con la frase "Penso che sia meglio non fare affidamento sulle ottimizzazioni del compilatore per far funzionare il codice in modo efficiente." Questo è fondamentalmente l'intero lavoro del compilatore. Il tuo compito è scrivere un codice sorgente chiaro, corretto e mantenibile. Per ogni problema di prestazioni che ho dovuto risolvere, ho dovuto risolvere un centinaio di problemi causati da uno sviluppatore che cercava di essere intelligente invece di fare qualcosa di semplice, corretto e manutenibile.
Diamo un'occhiata ad alcune delle cose che potresti fare per cercare di "aiutare" il compilatore e vedere come influiscono sulla manutenibilità del codice sorgente.
- Si potrebbe restituire i dati tramite riferimento
Ad esempio:
void hello(std::string& outString)
Restituzione di dati utilizzando un riferimento rende il codice alla chiamata posto difficile da leggere.È quasi impossibile dire quale funzione chiama lo stato mutato come un effetto collaterale e quale no. Anche se stai molto attento a qualificare i riferimenti, sarà difficile da leggere sul sito di chiamata. Considera il seguente esempio:
void hello(std::string& outString); //<-This one could modify outString
void out(const std::string& toWrite); //<-This one definitely doesn't.
. . .
std::string myString;
hello(myString); //<-This one maybe mutates myString - hard to tell.
out(myString); //<-This one certainly doesn't, but it looks identical to the one above
Anche la dichiarazione di saluto non è chiara. Modifica outString, o l'autore è semplicemente sciatto e si è dimenticato di const qualificare il riferimento? Il codice scritto in un functional style è più facile da leggere e comprendere e più difficile da interrompere accidentalmente.
Evitare
- Si potrebbe restituire un puntatore all'oggetto invece di restituire l'oggetto.
Restituire un puntatore all'oggetto rende difficile accertarsi che il codice sia corretto. A meno che non usi un unique_ptr devi avere fiducia che chiunque usi il tuo metodo sia completo e si assicuri di eliminare il puntatore quando ha finito, ma non è molto RAII. std :: string è già un tipo di wrapper RAII per un char * che astrae i problemi di durata dei dati associati alla restituzione di un puntatore. Restituire un puntatore a una stringa: std :: string semplicemente reintroduce i problemi che std :: string è stato progettato per risolvere. Affidarsi a un essere umano per essere diligente e leggere attentamente la documentazione per la propria funzione e sapere quando cancellare il puntatore e quando non cancellare il puntatore è improbabile che abbia un esito positivo.
Evitare
- che ci porta a spostare costruttori.
Un costruttore di spostamenti trasferirà semplicemente la proprietà dei dati puntati da "risultato" alla destinazione finale. Successivamente, l'accesso all'oggetto 'risultato' non è valido ma non importa: il metodo è terminato e l'oggetto 'risultato' è andato fuori dal campo di applicazione. Nessuna copia, solo un trasferimento di proprietà del puntatore con semantica chiara.
Normalmente il compilatore chiamerà il costruttore di spostamenti per te. Se sei davvero paranoico (o hai una conoscenza specifica che il compilatore non ti aiuterà) puoi usare std::move.
fare questo uno se possibile
Infine compilatori moderni sono sorprendenti. Con un moderno compilatore C++, il 99% delle volte il compilatore eseguirà una sorta di ottimizzazione per eliminare la copia. L'altro 1% delle volte probabilmente non ha importanza per le prestazioni. In determinate circostanze il compilatore può riscrivere un metodo come std :: string GetString(); annullare GetString (std :: string & outVar); automaticamente. Il codice è ancora facile da leggere, ma nell'assemblaggio finale si ottengono tutti i vantaggi di velocità reali o immaginari di ritorno per riferimento. Non sacrificare la leggibilità e la manutenibilità per le prestazioni, a meno che tu non abbia una conoscenza specifica che la soluzione non soddisfa i requisiti aziendali.
Non ottimizzare fino a quando non hai confermato che la soluzione non soddisfa i requisiti aziendali e quindi ottimizzare solo ciò che il profiler ti dice che è troppo lento. Lascia che sia il compilatore a gestirlo. Sarà più facile da leggere e mantenere. –
In C++ 11 questo quasi certamente utilizzerà un costruttore di mosse se per qualche ragione il RVO fallisce. Se si vuole essere assolutamente sicuri, passare la stringa di output in base al riferimento anziché restituirlo. RVO fa parte dello standard ora, anche se fino a quando si dispone di un compilatore moderno in genere si può fare affidamento su di esso. –
@PeteBaughman e Jonathan Potter: Mi rendo conto che il compilatore può ottimizzare questo aspetto, e questo non può causare un sovraccarico enorme nella mia applicazione, ma non è in grado di farlo in qualche modo un requisito di base? –