Copia di oggetti il costruttore di copia ha

  • Slides: 9
Download presentation
Copia di oggetti • il costruttore di copia ha le stesse particolarità della signature

Copia di oggetti • il costruttore di copia ha le stesse particolarità della signature di un costruttore ordinario; il primo parametro è una reference ad un oggetto della stessa classe. • il compilatore provvede alla generazione automatica di un costruttore di copia, se non è presente nella classe. // point 2 d. h . . . Point 2 D(const Point 2 D& other); // costruttore di copia nella sezione pubblica della classe. . . // point 2 d. cpp un rvalue associato ad una const . . . reference sopravvive oltre l’istruzione in cui è contenuto Geometry: : Point 2 D(const Point 2 D& other) { x = other. X(); y = other. Y(); } . . .

è possibile usare il costruttore di copia non solo su lvalue, cioè entità che

è possibile usare il costruttore di copia non solo su lvalue, cioè entità che possono essere dereferenziate, ma anche sul risultato di espressioni #include "point 2 d. h" using Geometry: : Point 2 D; int main() { Point 2 D p 1(10, 10); // chiama il costruttore Point 2 D(double, double) Point 2 D p 2(p 1); // ok, chiama il costruttore di copia Point 2 D p 3(Point 2 D(10, 10)); Ok solo se il costruttore di copia return 0; } accetta un valore temporaneo, cioè una const reference a p 3 è assegnato il risultato di un’espressione, cioè un valore temporaneo senza nome. Se il qualificatore const venisse omesso nel costruttore di copia della classe Point 2 D, la definizione di p 3 produrrebbe un errore di compilazione

Semantica del costruttore di copia • il costruttore di copia per la classe Point

Semantica del costruttore di copia • il costruttore di copia per la classe Point 2 D è uguale a quello che il compilatore avrebbe generato per noi. • la semantica del costruttore di copia generato automaticamente è quella della copia membro. • ma se una classe contiene dati membro che sono puntatori o reference, la copia membro di un oggetto può non essere sufficiente; in questo caso la copia membro è una copia “superficiale” (shallow copy). • un’istanza della classe ed una sua shallow copy sono legate; una modifica fatta ad un dato membro puntatore o reference di una si riflette automaticamente nell’altra, conseguenze che possono portare anche all’instabilità del programma. • è importante implementare il costruttore di copia correttamente, eliminando le dipendenze tra un oggetto e le sue copie.

// a. h class A { public: private: A(); ~A(); A(const A& other); int

// a. h class A { public: private: A(); ~A(); A(const A& other); int m; int *ptr; }; // a. cpp . . . A: : A(const A& other) { m = other. m; // shallow copy // ptr = other. ptr; // deep copy ptr = new int; if (other. ptr != nullptr) *ptr = *(other. ptr); } deep copy: è allocata la memoria per il dato membro ptr, e poi è copiato il valore puntato da other. ptr. Ok solo se il costruttore di copia accetta un valore temporaneo, cioè una const reference

L’operatore di assegnamento di copia è usato quando ad un oggetto viene riassegnato un

L’operatore di assegnamento di copia è usato quando ad un oggetto viene riassegnato un valore in una fase successiva alla sua dichiarazione, come nell’esempio seguente: Point 2 D a(1, 1); Point 2 D b(2, 2); a = b; // invoca il costruttore Point 2 D(double, double) // come sopra // invoca l'operatore di assegnamento di copia // point 2 d. h. . . Point 2 D& operator=(const Point 2 D& other); // operatore di assegnamento di copia definito nella sezione pubblica . . . // point 2 d. cpp . . . Geometry: : Point 2 D& Geometry: : Point 2 D: : operator=(const Point 2 D& other) { La firma dell’operatore di assegnamento è analoga a quella del x = other. x; costruttore di copia; y = other. y; la sua implementazione, a meno del return *this, dovrebbe essere return *this; } uguale a quella del costruttore. Anche l’operatore di assegnamento è generato automaticamente dal compilatore, se la nostra classe non ne definisce uno.

Return Value Optimization e elisione di copia La copia di oggetti è alla base

Return Value Optimization e elisione di copia La copia di oggetti è alla base di molte funzionalità del linguaggio C++: • assegnazione e costruzione di copie, • passaggio per valore dei parametri • copia del valore di ritorno di una funzione • algoritmi generici su strutture dati, eccetera. L’uso eccessivo della copia è una delle critiche rivolte al C++: cosa succede quando dobbiamo iterare la stessa funzione, ad esempio in un ciclo che scandisce una lista, o un altra struttura dati, con migliaia o milioni di oggetti complessi? La copia di un oggetto richiede memoria e cicli del processore. La copia di oggetti è l’operazione più costosa di tutte le istruzioni eseguite da una funzione. Nella fase di l’ottimizzazione del codice, lo standard C++ prescrive due azioni che possono essere applicate ogni volta che viene invocato il costruttore di copia.

ll caso più comune è quello dell’ottimizzazione del valore di ritorno di una funzione

ll caso più comune è quello dell’ottimizzazione del valore di ritorno di una funzione o RVO (return value optimization). L’ambito di visibilità del valore di ritorno di una funzione è limitato al corpo della funzione stessa; quando assegniamo ad una variabile il valore di ritorno di una funzione, in realtà il valore dovrebbe essere copiato nel contesto della chiamata a funzione. RVO è un’ottimizzazione che consiste nell’alterare la firma della funzione di modo da includere un parametro addizionale passato per riferimento, da usare al posto del valore di ritorno.

// funzione per il calcolo del punto medio di un segmento Point 2 D

// funzione per il calcolo del punto medio di un segmento Point 2 D middle. Point(Point 2 D a, Point 2 D b) { Point 2 D result; result. set. X((a. X() + b. X()) / 2. 0); result. set. Y((a. Y() + b. Y()) / 2. 0); return result; } . . . Point 2 D c = middle. Point(Point 2 D(1, 1), Point 2 D(1, 2)); // codice equivalente a seguito dell'ottimizzazione RVO operata dal compilatore void middle. Point(Point 2 D a, Point 2 D b, Point 2 D& result) { result. set. X((a. X() + b. X()) / 2. 0); RVO dipende dal modo in cui scriviamo il nostro codice. result. set. Y((a. Y() + b. Y()) / 2. 0); Se la funzione presenta più di un possibile valore di ritorno, ad } esempio dentro una condizione if–else, il compilatore potrebbe non essere in grado di ottimizzare il codice. . Point 2 D c; middle. Point(Point 2 D(1, 1), Point 2 D(1, 2), c);

La seconda forma di ottimizzazione è l’elisione della copia (copy elision). E’ introdotta dal

La seconda forma di ottimizzazione è l’elisione della copia (copy elision). E’ introdotta dal compilatore ogni volta che c’è un valore temporaneo: perchè sprecare risorse nel costruire un valore temporaneo al solo scopo di copiarlo in una variabile dello stesso tipo e distruggerlo immediatamente dopo? Point 2 D p 3(Point 2 D(10, 10)); // ok se il costruttore di copia accetta un valore temporaneo, una const reference La maggior parte dei compilatori per C++ ottimizzerà la copia del temporaneo Point 2 D(10, 10) in una invocazione diretta al costruttore della classe, esattamente come se avessimo scritto: Point 2 D p 3(10, 10);