Kopiera konstruktör (C++)
I programmeringsspråket C++ är en kopiakonstruktor en speciell konstruktor för att skapa ett nytt objekt som en kopia av ett befintligt objekt. Kopieringskonstruktörer är standardsättet för att kopiera objekt i C++, i motsats till kloning , och har C++-specifika nyanser.
Det första argumentet för en sådan konstruktor är en referens till ett objekt av samma typ som det konstrueras (const eller non-const), som kan följas av parametrar av vilken typ som helst (alla har standardvärden).
Normalt skapar kompilatorn automatiskt en kopiakonstruktor för varje klass (känd som en implicit kopiakonstruktor) men för speciella fall skapar programmeraren kopieringskonstruktorn, känd som en användardefinierad kopiakonstruktor. I sådana fall skapar inte kompilatorn någon. Därför finns det alltid en kopieringskonstruktor som antingen definieras av användaren eller av systemet.
En användardefinierad kopieringskonstruktor behövs i allmänhet när ett objekt äger pekare eller icke-delbara referenser , till exempel till en fil , i vilket fall en destruktor och en tilldelningsoperator också bör skrivas (se regel tre ).
Definition
Kopiering av objekt uppnås genom användning av en kopieringskonstruktör och en tilldelningsoperator . En kopiakonstruktor har som sin första parameter en (möjligen const eller volatile ) referens till sin egen klasstyp. Den kan ha fler argument, men resten måste ha standardvärden kopplade till dem. Följande skulle vara giltiga kopia konstruktorer för klass X
:
0
X ( konst X & copy_from_me ); X ( X & copy_from_me ); X ( flyktigt X & copy_from_me ); X ( const volatile X & copy_from_me ); X ( X & copy_from_me , int = ); X ( const X & copy_from_me , double = 1.0 , int = 42 ); ...
Den första bör användas om det inte finns en god anledning att använda någon av de andra. En av skillnaderna mellan den första och den andra är att temporärer kan kopieras med den första. Till exempel:
Xa = X () ; // giltigt givet X(const X& copy_from_me) men inte giltigt givet X(X& copy_from_me) // eftersom den andra vill att en icke-konst X& // ska skapa en, skapar kompilatorn först en temporär genom att anropa standardkonstruktorn // av X, använder sedan kopieringskonstruktorn för att initialisera som en kopia av den tillfälliga. // Tillfälliga objekt som skapas under programkörning är alltid av const-typ. Så, nyckelord const krävs. // För vissa kompilatorer fungerar båda versionerna faktiskt men det här beteendet bör man inte lita på // eftersom det inte är standard.
En liknande skillnad gäller när man direkt försöker kopiera ett const-
objekt:
const Xa ; _ Xb = a ; _ // giltigt givet X(konst X& copy_from_me) men inte giltigt givet X(X& copy_from_me) // eftersom den andra vill ha en icke-konst X&
X &
-formen för kopieringskonstruktorn används när det är nödvändigt att modifiera det kopierade objektet. Detta är mycket sällsynt men det kan ses användas i standardbibliotekets std::auto_ptr
. En referens måste tillhandahållas:
Xa ; _ Xb = a ; _ // giltigt om någon av kopieringskonstruktörerna är definierade // eftersom en referens skickas.
Följande är ogiltiga kopieringskonstruktorer eftersom copy_from_me
inte skickas som referens ( &
) :
X ( X copy_from_me ); X ( const X copy_from_me );
eftersom anropet till dessa konstruktörer också skulle kräva en kopia, vilket skulle resultera i ett oändligt rekursivt anrop.
Följande fall kan resultera i ett anrop till en kopieringskonstruktör:
- När ett objekt returneras av värde
- När ett objekt skickas (till en funktion) av värde som ett argument
- När ett föremål kastas
- När ett objekt fångas av värde
- När ett objekt placeras i en initieringslista med parentes
Dessa fall kallas gemensamt för kopieringsinitiering och är ekvivalenta med: T x = a;
Det är dock inte garanterat att en kopieringskonstruktör kommer att anropas i dessa fall, eftersom C++-standarden tillåter kompilatorn att optimera bort kopian i vissa fall, ett exempel är returvärdeoptimeringen ( ibland kallad RVO).
Drift
Ett objekt kan tilldelas värde med en av de två teknikerna:
- Explicit tilldelning i ett uttryck
- Initialisering
Explicit tilldelning i ett uttryck
Objekt a ; Objekt b ; a = b ; // översätts som Object::operator=(const Object&), sålunda kallas a.operator=(b) // (anropa enkel kopia, inte kopieringskonstruktor!)
Initialisering
Ett objekt kan initieras på något av följande sätt.
a. Genom deklaration
Objekt b = a ; // översätts som Object::Object(const Object&) (anropa copy constructor)
b. Genom funktionsargument
typ funktion ( Objekt a );
c. Genom funktion returvärde
Objekt a = funktion ();
Kopieringskonstruktorn används endast för initialiseringar och gäller inte tilldelningar där uppdragsoperatorn används istället.
Den implicita kopieringskonstruktören i en klass anropar baskopiekonstruktörer och kopierar dess medlemmar med hjälp av lämpliga metoder för deras typ. Om det är en klasstyp anropas kopieringskonstruktorn. Om det är en skalär typ används den inbyggda tilldelningsoperatorn. Slutligen, om det är en array, kopieras varje element på det sätt som är lämpligt för dess typ.
Genom att använda en användardefinierad kopieringskonstruktor kan programmeraren definiera beteendet som ska utföras när ett objekt kopieras.
Exempel
Dessa exempel illustrerar hur kopieringskonstruktörer fungerar och varför de ibland krävs.
Implicit kopia konstruktör
Tänk på följande exempel:
#include <iostream> class Person { public : explicit Person ( int age ) : age ( age ) {} int age ; }; int main () { Person timmy ( 10 ); Person Sally ( 15 ); Person timmy_clone = timmy ; std :: cout << timmy . ålder << " " << sally . ålder << " " << timmy_clone . ålder << std :: endl ; timmy . ålder = 23 ; std :: cout << timmy . ålder << " " << sally . ålder << " " << timmy_clone . ålder << std :: endl ; }
Produktion
10 15 10 23 15 10
Som väntat har timmy
kopierats till det nya objektet, timmy_clone
. Medan timmys
ålder ändrades, förblev timmy_clones
ålder densamma. Detta beror på att de är helt olika objekt.
Kompilatorn har genererat en kopia konstruktor för oss, och den kan skrivas så här:
Person ( const Person & other ) : age ( other . age ) // Anropar kopians konstruktor för åldern. { }
Så när behöver vi verkligen en användardefinierad kopiakonstruktor? Nästa avsnitt kommer att utforska den frågan.
Användardefinierad kopia konstruktor
Tänk på en mycket enkel dynamisk arrayklass som följande:
0
0 0
0
#include <iostream> class Array { public : explicit Array ( int size ) : size ( size ), data ( new int [ size ]) {} ~ Array () { if ( data != nullptr ) { delete [] data ; } } int storlek ; int * data ; }; int main () { Array first ( 20 ); först . data [ ] = 25 ; { Array copy = first ; std :: cout << först . data [ ] << " " << kopia . data [ ] << std :: endl ; } // (1) först . data [ ] = 10 ; // (2) }
Produktion
25 25 Segmenteringsfel
Eftersom vi inte angav en kopia konstruktor genererade kompilatorn en åt oss. Den genererade konstruktorn skulle se ut ungefär så här:
Array ( const Array & other ) : storlek ( annan . storlek ), data ( annan . data ) {}
Problemet med denna konstruktor är att den utför en ytlig kopia av datapekaren . Den kopierar bara adressen till den ursprungliga datamedlemmen; det betyder att de båda delar en pekare till samma minnesbit, vilket inte är vad vi vill ha. När programmet når rad (1) anropas kopians destruktor (eftersom objekt på stacken förstörs automatiskt när deras omfång tar slut). Arrays destruktor tar bort datamatrisen för originalet, därför, när den raderade kopians data, eftersom de delar samma pekare, raderade den också först data. Rad (2) kommer nu åt ogiltiga data och skriver till den. Detta ger ett segmenteringsfel .
Om vi skriver vår egen kopieringskonstruktor som utför en djupkopia försvinner detta problem.
// för std::copy #include <algorithm> Array ( const Array & other ) : storlek ( annan . storlek ), data ( new int [ annan . storlek ]) { std :: copy ( annan . data , annan . data + annan storlek , data ) ; }
Här skapar vi en ny int- array och kopierar innehållet till den. Nu andras förstörare bara sina data, och inte förstas data. Linje (2) kommer inte att producera ett segmenteringsfel längre.
Istället för att göra en djupkopia direkt, finns det några optimeringsstrategier som kan användas. Dessa gör att du säkert kan dela samma data mellan flera objekt, vilket sparar utrymme. Kopiera -på-skriv- strategin gör en kopia av data endast när den skrivs till. Referensräkning behåller räkningen av hur många objekt som refererar till data, och raderar den endast när denna räkning når noll (t.ex. boost::shared_ptr )
.
Bitvis kopia konstruktör
Det finns inget sådant som "bitwise copy constructor" i C++. Den standardgenererade kopiakonstruktorn kopierar dock genom att anropa kopiakonstruktörer på medlemmar, och för en råpekarmedlem kommer detta att kopiera råpekaren (dvs inte en djupkopia).
Logisk kopia konstruktör
En logisk kopia konstruktor gör en sann kopia av strukturen såväl som dess dynamiska strukturer. Logiska kopieringskonstruktörer kommer in i bilden främst när det finns pekare eller komplexa objekt i objektet som kopieras.
Explicit kopia konstruktör
En explicit kopieringskonstruktor är en som förklaras explicit genom att använda det explicita nyckelordet. Till exempel:
explicit X ( const X & copy_from_me );
Den används för att förhindra kopiering av objekt vid funktionsanrop eller med kopieringsinitieringssyntaxen.