Kopírovat konstruktor (C ++) - Copy constructor (C++)

V C ++ programovací jazyk, a kopírovací konstruktor je speciální konstruktor pro vytvoření nového objekt jako kopie existujícího objektu. Konstruktory kopírování jsou standardní způsob kopírování objektů v C ++, na rozdíl od nich klonování a mají specifické nuance pro C ++.

Prvním argumentem takového konstruktoru je odkaz na objekt stejného typu, jaký je právě konstruován (const nebo non-const), za kterým mohou následovat parametry jakéhokoli typu (všechny mají výchozí hodnoty).

Normálně překladač automaticky vytvoří konstruktor kopírování pro každého třída (známý jako implicitní copy constructor), ale pro zvláštní případy programátor vytvoří konstruktor kopírování, známý jako definované uživatelem kopírovací konstruktor. V takových případech jej kompilátor nevytvoří. Proto vždy existuje jeden konstruktor kopie, který je definován buď uživatelem, nebo systémem.

Když objekt vlastní, je obecně potřeba konstruktor kopírování definovaný uživatelem ukazatele nebo nelze sdílet Reference, jako například a soubor, v takovém případě a destruktor a operátor přiřazení by také měly být psány (viz Pravidlo tří ).

Definice

Kopírování objektů je dosaženo použitím konstruktoru kopírování a operátor přiřazení. Konstruktor kopie má jako svůj první parametr a (případně const nebo nestálý ) odkaz na svůj vlastní typ třídy. Může mít více argumentů, ale zbytek musí mít přidružené výchozí hodnoty.[1] Následující by byly platné konstruktory kopií pro třídu X:

X(konst X& copy_from_me);X(X& copy_from_me);X(nestálý X& copy_from_me);X(konst nestálý X& copy_from_me);X(X& copy_from_me, int = 0);X(konst X& copy_from_me, dvojnásobek = 1.0, int = 42);...

První z nich by měl být použit, pokud není dobrý důvod použít jednu z ostatních. Jedním z rozdílů mezi prvním a druhým je to, že dočasné lze kopírovat s prvním. Například:

X A = X();     // platné vzhledem k X (const X & copy_from_me), ale neplatné vzhledem k X (X & copy_from_me)               // protože druhý chce nekonstantní X &               // pro vytvoření a kompilátor nejprve vytvoří dočasný vyvoláním výchozího konstruktoru               // of X, then uses the copy constructor to initialize as a copy of that temporary.                // Dočasné objekty vytvořené během provádění programu jsou vždy typu const. Je tedy vyžadováno klíčové slovo const.               // U některých překladačů obě verze skutečně fungují, ale na toto chování by se nemělo spoléhat                // na protože je to nestandardní.

Další rozdíl mezi nimi je zřejmý:

konst X A;X b = A;       // platné vzhledem k X (const X & copy_from_me), ale neplatné vzhledem k X (X & copy_from_me)               // protože druhý chce nekonstantní X &

The X& forma konstruktoru kopírování se používá, když je nutné upravit kopírovaný objekt. To je velmi vzácné, ale lze jej vidět ve standardní knihovně std :: auto_ptr. Musí být uveden odkaz:

X A;X b = A;       // platné, pokud je definován některý z konstruktorů kopírování               // protože je předáván odkaz.

Následují konstruktory neplatných kopií (důvod - copy_from_me není předán jako reference):

X(X copy_from_me);X(konst X copy_from_me);

protože volání těchto konstruktorů by vyžadovalo také kopii, což by mělo za následek nekonečně rekurzivní volání.

Následující případy mohou mít za následek volání konstruktoru kopie:

  1. Když je objekt vrácen podle hodnoty
  2. Když je objekt předán (funkci) hodnotou jako argument
  3. Když je hozen předmět
  4. Když je objekt chycen
  5. Když je objekt umístěn do seznamu inicializátorů uzavřeného v závorkách

Tyto případy se nazývají souhrnně inicializace kopie a jsou ekvivalentní:[2]T x = a;

Není však zaručeno, že v těchto případech bude volán konstruktor kopie, protože C ++ Standard umožňuje kompilátoru optimalizovat kopii v určitých případech, jedním příkladem je optimalizace návratové hodnoty (někdy označované jako RVO).

Úkon

Objektu lze přiřadit hodnotu pomocí jedné ze dvou technik:

  • Explicitní přiřazení ve výrazu
  • Inicializace

Explicitní přiřazení ve výrazu

Objekt A;Objekt b;A = b;       // překládá se jako Object :: operator = (const Object &), takže se volá a.operator = (b)              // (vyvolat jednoduchou kopii, ne kopírovací konstruktor!)

Inicializace

Objekt lze inicializovat kterýmkoli z následujících způsobů.

A. Prostřednictvím prohlášení

Objekt b = A; // překládá se jako Object :: Object (const Object &) (vyvolat konstruktor kopie)

b. Prostřednictvím argumentů funkcí

typ funkce(Objekt A);

C. Prostřednictvím návratové hodnoty funkce

Objekt A = funkce();

Konstruktor kopírování se používá pouze pro inicializace a nevztahuje se na přiřazení, kde se místo toho používá operátor přiřazení.

Konstruktor implicitní kopie třídy volá konstruktory základní kopie a kopíruje její členy prostředky odpovídající jejich typu. Pokud se jedná o typ třídy, volá se konstruktor kopírování. Pokud se jedná o skalární typ, použije se integrovaný operátor přiřazení. Nakonec, pokud se jedná o pole, každý prvek se zkopíruje způsobem odpovídajícím jeho typu.[3]

Pomocí uživatelského konstruktoru kopírování může programátor definovat chování, které se má provést při kopírování objektu.

Příklady

Tyto příklady ilustrují, jak konstruktory kopií fungují a proč jsou někdy vyžadovány.

Implicitní konstruktor kopie

Zvažte následující příklad:

#zahrnout <iostream>třída Osoba { veřejnost:  explicitní Osoba(int stáří) : stáří(stáří) {}  int stáří;};int hlavní() {  Osoba Timmy(10);  Osoba výpad(15);  Osoba timmy_clone = Timmy;  std::cout << Timmy.stáří << " " << výpad.stáří << " " << timmy_clone.stáří            << std::konec;  Timmy.stáří = 23;  std::cout << Timmy.stáří << " " << výpad.stáří << " " << timmy_clone.stáří            << std::konec;}

Výstup

10 15 1023 15 10

Podle očekávání, Timmy byl zkopírován do nového objektu, timmy_clone. Zatímco Timmy věk byl změněn, timmy_clone věk zůstal stejný. Je to proto, že se jedná o zcela odlišné objekty.

Kompilátor pro nás vygeneroval kopírovací konstruktor a mohl by být napsán takto:

Osoba(konst Osoba& jiný)     : stáří(jiný.stáří)  // Volá konstruktor kopií věku.{}

Kdy tedy opravdu potřebujeme konstruktor kopírování definovaný uživatelem? Následující část tuto otázku prozkoumá.

Uživatelsky definovaný konstruktor kopií

Zvažte velmi jednoduché dynamické pole třída jako následující:

#zahrnout <iostream>třída Pole { veřejnost:  explicitní Pole(int velikost) : velikost(velikost), data(Nový int[velikost]) {}  ~Pole() {    -li (data != nullptr) {      vymazat[] data;    }  }  int velikost;  int* data;};int hlavní() {  Pole za prvé(20);  za prvé.data[0] = 25;  {    Pole kopírovat = za prvé;    std::cout << za prvé.data[0] << " " << kopírovat.data[0] << std::konec;  }  // (1)  za prvé.data[0] = 10;  // (2)}

Výstup

25 25 Chyba segmentace

Protože jsme neurčili konstruktor kopírování, překladač nám vygeneroval jeden. Generovaný konstruktor by vypadal nějak takto:

Pole(konst Pole& jiný)  : velikost(jiný.velikost), data(jiný.data) {}

Problém tohoto konstruktoru spočívá v tom, že provádí a mělká kopie z data ukazatel. Zkopíruje pouze adresu původního datového člena; to znamená, že oba sdílejí ukazatel na stejnou část paměti, což není to, co chceme. Když program dosáhne řádku (1), kopie zavolá se destruktor (protože objekty v zásobníku jsou zničeny automaticky, když skončí jejich rozsah). Pole destruktor vymaže data pole originálu, proto při jeho mazání kopie data, protože sdílejí stejný ukazatel, byla také odstraněna první data. Čára (2) nyní přistupuje k neplatným datům a zapisuje je! To vytváří nechvalně známé Porucha Segmentace.

Pokud napíšeme vlastní konstruktor kopírování, který provádí a hluboká kopie pak tento problém zmizí.

// pro std :: copy#zahrnout <algorithm>Pole(konst Pole& jiný)    : velikost(jiný.velikost), data(Nový int[jiný.velikost]) {  std::kopírovat(jiný.data, jiný.data + jiný.velikost, data); }

Zde vytváříme nový int pole a kopírování obsahu do něj. Nyní, ostatní destruktor smaže pouze svá data, a nikoli první data. Čára (2) již neprodukuje poruchu segmentace.

Místo toho, abyste okamžitě provedli hlubokou kopii, lze použít některé optimalizační strategie. Ty vám umožňují bezpečně sdílet stejná data mezi několika objekty, čímž šetří místo. The copy-on-write strategie vytvoří kopii dat, pouze když jsou zapsána. Počítání referencí zachovává počet, kolik objektů odkazuje na data, a odstraní je pouze v případě, že tento počet dosáhne nuly (např. boost :: shared_ptr).

Zkopírujte konstruktory a šablony

Na rozdíl od očekávání není konstruktor kopírování šablony uživatelsky definovaným konstruktorem kopírování. Nestačí tedy mít pouze:

šablona <typename A> Pole::Pole(A konst& jiný)    : velikost(jiný.velikost()), data(Nový int[jiný.velikost()]) {  std::kopírovat(jiný.začít(), jiný.konec(), data);}

(Všimněte si, že typ A může být Pole.) Pro konstrukci Array z Array musí být také poskytnut uživatelem definovaný konstruktor kopie bez šablony.

Konstruktor bitové kopie

V jazyce C ++ neexistuje nic jako „konstrukt bitového kopírování“. Výchozí generovaný konstruktor kopírování však kopíruje vyvoláním konstruktorů kopírování na členy a pro člena surového ukazatele to zkopíruje surový ukazatel (tj. Ne hlubokou kopii).

Konstruktor logické kopie

Je vidět, že v konstruktoru logické kopie je vytvořena nová dynamická členská proměnná pro ukazatel spolu s kopírováním hodnot. [4]

Konstruktor logické kopie vytvoří skutečnou kopii struktury i jejích dynamických struktur. Konstruktory logického kopírování přicházejí do obrázku hlavně tehdy, když v kopírovaném objektu jsou ukazatele nebo složité objekty.

Konstruktor explicitní kopie

Konstruktor explicitní kopie je ten, který je deklarován jako explicitní pomocí explicitní klíčové slovo. Například:

explicitní X(konst X& copy_from_me);

Používá se k zabránění kopírování objektů při volání funkce nebo se syntaxí inicializace kopírování.

Viz také

Reference

  1. ^ INCITS ISO IEC 14882-2003 12.8.2. [1] Archivováno 8. června 2007 v Wayback Machine
  2. ^ ISO /IEC (2003). ISO / IEC 14882: 2003 (E): Programming Languages ​​- C ++ §8.5 Inicializátory [dcl.init] odst. 12
  3. ^ INCITS ISO IEC 14882-2003 12.8.8. [2] Archivováno 8. června 2007 v Wayback Machine
  4. ^ Počítačová věda Strukturovaný přístup pomocí C ++ od Behrouze A. Forouzana a Richarda F. Gilberga, obrázek 10-9, strana 507

externí odkazy