Constructor de copiere

Versiunea actuală a paginii nu a fost încă examinată de colaboratori experimentați și poate diferi semnificativ de versiunea revizuită la 31 martie 2015; verificările necesită 16 modificări .

Un constructor de copiere este un  constructor special în limbajul de programare C++ și în alte limbaje de programare, cum ar fi Java , folosit pentru a crea un nou obiect ca o copie a unuia existent. Un astfel de constructor ia cel puțin un argument: o referință la obiectul de copiat.

În mod normal , compilatorul va genera automat un constructor de copiere pentru fiecare clasă (cunoscut sub numele de constructori de copiere impliciti , adică constructori de copiere care sunt specificați implicit), dar în unele cazuri, programatorul va crea un constructor de copiere, care este apoi numit explicit . constructor de copiere (sau „constructor de copiere specificat în mod explicit”). În astfel de cazuri, compilatorul nu generează constructori impliciti.

Constructorul de copiere este necesar în cea mai mare parte atunci când obiectul are un pointer sau o referință nepartajată , cum ar fi un fișier , caz în care veți avea nevoie, de obicei, de un destructor și de un operator de atribuire (vezi regula celor trei ).

Definiție

Copierea obiectelor se face folosind constructorul de copiere și operatorul de atribuire . Constructorul de copiere ia ca prim parametru (cu modificatorul opțional de tip const sau volatil) o referință la propriul tip de clasă. Pe lângă acest parametru, poate avea mai mulți parametri suplimentari, cu condiția ca acești parametri suplimentari să fie setați la valorile implicite [1] . Următorul exemplu demonstrează constructori de copiere validi pentru clasa X:

X ( const X & ); X ( X & ); X ( const volatil X & ); X ( volatil X & ); X ( const X & , int = 10 ); X ( const X & , dublu = 1,0 , int = 40 );

Prima intrare a constructorului de copiere este primară; alte forme ar trebui folosite numai atunci când este necesar. Puteți copia numai obiecte temporare folosind primul constructor. De exemplu:

Xa = X ( ); // Se va compila dacă constructorul X(const X&) este implementat și va arunca o eroare // dacă este definit doar X(X&). // Pentru a crea un obiect a, compilatorul va crea un obiect temporar din clasa // X și apoi va folosi constructorul de copiere pentru a crea un obiect a. // Copierea obiectelor temporare necesită un tip const.

În exemplul de mai jos, obiectul a este creat ca imuabil, deci la crearea obiectului b , este necesar primul constructor de copiere.

const X a ; X b = a ; // corect dacă există X(const X&) și nu corect dacă există X(X&) // deoarece al doilea nu acceptă tipul const X&

Tipul X&de constructor de copiere este utilizat atunci când este necesară modificarea obiectului copiat. Aceasta este o situație destul de rară, dar este furnizată în biblioteca standard apelând std::auto_ptr. Link-ul trebuie să implementeze:

X a ; X b = a ; // corectează dacă oricare dintre constructorii de copiere este definit // de când a fost transmisă referința

Următorii constructori de copiere (sau constructori constanti) sunt invalidi:

X ( X ); X ( const X );

deoarece apelarea acestor constructori va necesita o altă copie, care va duce la un apel recursiv infinit (adică o buclă infinită).

Există patru cazuri de apelare a unui constructor de copiere:

  1. Când un obiect este o valoare returnată
  2. Când un obiect este transmis (la o funcție) prin valoare ca argument
  3. Când un obiect este construit dintr-un alt obiect (din aceeași clasă)
  4. Când compilatorul generează un obiect temporar (ca în primul și al doilea caz de mai sus; ca o conversie explicită etc.)

Operațiuni

Un obiect poate primi o valoare într-unul din două moduri:

  • Atribuire explicită într-o expresie
  • Inițializare

Atribuire explicită într-o expresie

Obiectul A ; Obiectul B ; A = B ; // tradus ca Object::operator=(const Object&), // apelând astfel A.operator=(B)

Inițializare

Un obiect poate fi inițializat în oricare dintre următoarele moduri:

A. Inițializarea la declarație

Obiectul B = A ; // tradus ca Object::Object(const Object&)

b. Inițializarea la transmiterea argumentelor către funcții

funcția de tip ( Object a );

c. Când returnați o valoare a funcției

Obiectul a = funcția ();

Constructorul de copiere este utilizat numai în cazul inițializării și nu este utilizat în locul unei atribuiri explicite (adică, în cazul în care este utilizat operatorul de atribuire ).

Constructorul implicit de copiere a clasei apelează constructorii de copie a claselor de bază și face copii pe biți ale membrilor clasei. Dacă un membru al clasei este o clasă, atunci constructorul său de copiere este apelat. Dacă este un tip scalar (tip POD în C++), atunci se folosește operatorul de atribuire încorporat. Și, în sfârșit, dacă este o matrice, atunci fiecare element al matricei este copiat în modul corespunzător tipului său. [2]

Folosind un constructor de copiere explicită, programatorul poate determina ce să facă după ce obiectul a fost copiat.

Exemple

Următoarele exemple ilustrează cum funcționează constructorii de copiere și de ce sunt necesari.

Constructorul implicit de copiere

#include <iostream> persoana de clasa { public : int vârsta ; Persoană ( vârstă ) : vârstă ( vârstă ) { } }; int main () { persoana timmy ( 10 ); persoana sally ( 15 ); Persoana timmy_clone = timmy ; std :: cout << timmy . vârsta << " " << sally . vârsta << " " << timmy_clone . varsta << std :: endl ; timmy . vârsta = 23 ; std :: cout << timmy . vârsta << " " << sally . vârsta << " " << timmy_clone . varsta << std :: endl ; }

Rezultat

10 15 10 23 15 10

După cum era de așteptat, timmy a fost copiat în noul obiect timmy_clone . La schimbarea vârstei (vârstei) lui timmy , vârsta lui timmy_clone nu s-a schimbat: obiectele sunt complet independente.

Compilatorul a generat un constructor de copiere pentru noi, care ar putea fi scris cam așa:

Persoană ( Person const & copy ) : vârstă ( copie . vârstă ) {}

Constructor de copiere explicită

Următorul exemplu arată o clasă de matrice dinamică simplă:

#include <iostream> Clasa Array { public : intsize ; _ int * date ; Matrice ( dimensiune int ) : dimensiune ( dimensiune ), date ( int nou [ dimensiune ]) {} ~ Matrice () { șterge [] date ; } }; int main () { Mai întâi matrice ( 20 ); primul . date [ 0 ] = 25 ; { Array copy = primul ; std :: cout << first . date [ 0 ] << " " << copiere . date [ 0 ] << std :: endl ; } // (1) primul . date [ 0 ] = 10 ; // (2) }

Rezultat

25 25 Eroare de segmentare

Aici compilatorul a generat automat constructorul de copiere. Acest constructor arată astfel:

Matrice ( Matrice const și copiere ) : dimensiune ( copiere . dimensiune ), date ( copiere . date ) {}

Problema cu acest constructor este că face o copie simplă a indicatorului de date . Copiază doar adresa, nu datele în sine. Și când programul ajunge la linia (1) , se apelează destructorul de copiere (obiectele din stivă sunt distruse automat când ajung la granițele lor). După cum puteți vedea, destructorul Array șterge matricea de date , așa că atunci când șterge datele copiei , șterge și datele primei . Linia (2) primește acum date incorecte și le scrie. Aceasta duce la faimoasa eroare de segmentare .

În cazul unui constructor de copie nativ care realizează o copie profundă , această problemă nu va apărea:

Matrice ( Matrice const și copiere ) : dimensiune ( copiere . dimensiune ), date ( new int [ copiere . dimensiune ]) { std :: copiere ( copiere . date , copiere . date + copiere . dimensiune , date ); // #include <algoritm> pentru std::copy }

Aici este creată o nouă matrice int și conținutul este copiat în ea. Acum , destructorul copiei își va șterge doar datele și nu va atinge datele mai întâi . Linia (2) nu mai provoacă o eroare de segmentare.

În loc de a efectua o copie profundă, pot fi utilizate mai multe strategii de optimizare. Acest lucru va permite accesul la date pentru mai multe obiecte într-un mod sigur, economisind astfel memorie. Strategia de copiere la scriere creează o copie a datelor numai atunci când sunt scrise. Numărul de referințe conține un contor al numărului de obiecte care fac referire la date și îl elimină numai atunci când contorul ajunge la zero (de exemplu, boost::shared_ptr).

Copiați constructorii și șabloanele

Constructorul de șabloane nu este un constructor de copiere .

template < typename T > Array :: Array ( const T & copy ) : dimensiune ( copiere . dimensiune ()), date ( new int [ copiere . dimensiune ()]) { std :: copy ( copie . begin (), copy . end (), data ); }

Acest constructor nu va fi folosit dacă T este de tip Array.

Matrice arr ( 5 ); Matrice arr2 ( arr );

A doua linie va apela fie constructorul de copiere care nu este șablon, fie, dacă nu există, constructorul de copiere implicit.

Vezi și

Note

  1. INCITS ISO IEC 14882-2003 12.8.2. [1] Arhivat pe 8 iunie 2007 la Wayback Machine
  2. INCITS ISO IEC 14882-2003 12.8.8. [2] Arhivat pe 8 iunie 2007 la Wayback Machine