Ottimizzare C++/Ottimizzazione del codice C++/Costruzioni e distruzioni: differenze tra le versioni

Contenuto cancellato Contenuto aggiunto
RamaccoloBot (discussione | contributi)
Nessun oggetto della modifica
Riga 2:
 
Spesso capita che per elaborare un'espressione venga creato un oggetto temporaneo, che viene distrutto alla fine della stessa espressione in cui viene creato.
Se tale oggetto è di un tipo fondamentale, il compilatore quasi sempre riesce a evitarne la creazione, e comunque la creazione e la distruzione di un oggetto di un tipo fondamentale sono abbastanza veloci.
SeInvece, se l'oggetto è invece di un tipo composito, la sua creazione e distruzionela possonosua averedistruzione hanno un costo arbitrariamente altoillimitato, in quanto comportano la chiamata di un costruttore e di un distruttore, che possono avere qualunque durata.
 
In questa sezione si descrivono alcune tecniche per evitare che siano creati oggetti temporanei di tipo composito, e quindi che siano chiamati i relativi costruttori e distruttori.
 
=== Valore di ritorno di funzioni ===
 
'''Per le funzioni che non siano espanse ''inline'', cerca di dichiarare ilun tipo di ritorno <code>void</code>tale oche <code>bool</code>la ocopia interodi o puntatore o riferimento. Comunque, evitaoggetti di dichiarare untale tipo dinon ritornosposta lapiù cui copia sposta oltredi 8 byte. Se non fosse fattibile, almeno costruisci l'oggetto da ritornare nelle stesse istruzioni <code>return</code>.'''
 
Nella compilazione di una funzione non espansa ''inline'', il compilatore non può sapere se il valore di ritorno verrà usato, e quindi lo deve comunque generare.
Generare un interooggetto ola uncui puntatorecopia onon unsposta riferimentopiù di 8 byte costa poco o niente, ma generare numeri a virgola mobile od oggetti più complessi richiede tempo.
Se lal'oggetto copiatemporaneo comporta l'allocazionepossiede didelle risorse, il tempo richiesto è enormemente maggiore, ma anche senza allocazioni, il tempo richiesto cresce al crescere del numero delle word che vengono copiate quando si copia un oggetto di tale tipo.
 
Comunque, se si costruisce l'oggetto da ritornare nelle stesse istruzioni <code>return</code>, senza quindi assegnare tale valore a una variabile, si sfrutta l'ottimizzazione garantita dallo standard detta ''Return Value Optimization'', che previene la creazione di oggetti temporanei.
 
Alcuni compilatori riescono a evitare la creazione di oggetti temporanei, anche se questi sono legati a variabili locali (con la cosiddetta ''Named Return Value Optimization''), ma in generale questo non è garantito e ha comunque alcune limitazioni.
 
Per verificare se viene attuata una di tali ottimizzazioni, incrementa un contatore statico nei costruttori, neinel distruttoridistruttore, e negli operatorinell'operatore di assegnamento della classe dell'oggetto ritornato.
Nel caso non risultassero applicate ottimizzazioni, ricorri a una delle seguenti tecniche alternative:
* Rendi la funzione <code>void</code>, e aggiungile un argomento passato per riferimento, che funge da valore di ritorno.
* Trasforma la funzione in un costruttore del tipo ritornato, che riceve gli stessi parametri della funzione.
* Fai in modo che la funzione restituisca un oggetto di un tipo ausiliario che ruba le risorse e le cede all'oggetto destinazione, senza copiarle.
* Usa un [[w:en:Expression_template|''expression template'']], che è una tecnica avanzata, facente parte del paradigma di programmazione detto [[w:en:Template metaprogramming|Template metaprogramming]].
* UsaSe usi lo standard C++0x, usa un ''rvalue reference'', introdotto dallo standard C++0x.
 
=== Spostamento di variabili all'esterno di cicli ===
 
'''Se una variabile è dichiarata all'interno di un ciclo, e l'assegnamento ad essa costa di meno di una costruzione epiù una distruzione, sposta tale dichiarazione prima del ciclo.'''
 
Se la variabile è dichiarata all'interno del ciclo, l'oggetto associato ad essa viene costruito e distrutto a ogni iterazione, mentre se è esterna al ciclo, tale oggetto viene costruito e distrutto una volta sola, ema presumibilmente viene invece assegnato una volta in più nel corpo del ciclo.
 
Tuttavia, in molti casi un assegnamento costa esattamente quanto una coppia costruzione+distruzione, per cui in tali casi non ci sono vantaggi a spostare la dichiarazione all'esterno e aggiungere un assegnamento all'interno.
Line 36 ⟶ 37:
=== Operatore di assegnamento ===
 
'''In un overload dell'operatore di assegnamento (operator=), se sei sicuro che non solleverà eccezioni, noncopia usareogni lavariabile tecnicamembro, invece di usare l'idioma ''copy&swap'', ma copia i singoli membri.'''
 
La tecnica più efficiente per copiare un oggetto è imitare iluna corretta lista di inizializzazione di un costruttore di copia, cioè, prima, copiaresi glichiama oggettil'analoga funzione membro delle classi base, e poi glisi copiano tutte le oggettivariabili membro, in ordine di dichiarazione.
 
TuttaviaPurtroppo, tale tecnica non è ''exception-safe'', cioè correse ildurante rischioquesta dioperazione nonviene chiamaresollevata ilun'eccezione, distruttorei distruttori di qualchealcuni oggettosotto-oggetti nelgià casocostruiti vengapotrebbero sollevatanon un'eccezione durantevenire lamai copiachiamati.
Pertanto, se c'è la possibilità che durante la copia venga sollevata un'eccezione, si deve usare una tecnica ''exception-safe'', che tuttavia non avrà prestazioni ottimali.
 
La tecnica di assegnamento ''exception-safe'' più elegante è quella dettal'idioma ''copy&swap'', esemplificata dal seguente codice in cui <code>C</code> rappresenta il nome della classe:.
Viene mostrata dal seguente codice, nel quale <code>C</code> rappresenta il nome della classe, e <code>swap</code> una funzione membro che dovrà essere definita:
 
<source lang=cpp>
Line 53 ⟶ 56:
=== Overload per evitare conversioni ===
 
'''DefinisciPer evitare costose conversioni di tipo, definisci delle funzioni in overload per i tipi di argomento più comune, per evitare conversioni.'''
 
Supponiamo di aver scritto la seguente funzione:
Spesso si vuole chiamare una funzione passando come argomento un'espressione che non è del tipo dell'argomento, ma può essere convertita in tale tipo.
 
<source lang=cpp>
int f(const std::string& s) { return s[0]; }
</source>
 
il cui scopo è consentire di scrivere il segueente codice:
 
<source lang=cpp>
std::string s("abc");
int n = f(s);
</source>
 
Tale funzione può però essere usata anche dal seguente codice:
 
<source lang=cpp>
int n = f(string("abc"));
</source>
 
E, grazie alla conversione implicita da <code>char*</code> a <code>std::string</code>, può essere usata anche dal seguente codice:
 
<source lang=cpp>
int n = f("abc");
</source>
 
Entrambe le due ultime chiamate alla funzione <code>f</code> sono inefficienti, perché creano un oggetto temporaneo <code>std::string</code> non vuoto.
 
Per mantenere l'efficienza della prima chiamata dell'esempio, si dovrebbe definire anche la seguente funzione in overload:
 
<source lang=cpp>
int f(const char* s) { return s[0]; }
</source>
 
In generale, se una funzione è chiamata passandole un argomento di un tipo non consentito ma che può venire implicitamente convertito a un tipo consentito, viene creato un oggetto temporaneo del tipo consentito.
Tale conversione, sia esplicita che implicita, crea un oggetto temporaneo.
 
TalvoltaPer èevitare possibiletale creareoggetto untemporaneo, si deve definire una funzione in overload dellarispetto alla funzione originale, che prendeprenda un argomento proprio del tipo corrispondentedell'effettivo oggetto passato, evitando quindicosì la necessità di una conversione.
 
[[Categoria:Ottimizzare C++|Costruzioni e distruzioni]]