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:

  1. När ett objekt returneras av värde
  2. När ett objekt skickas (till en funktion) av värde som ett argument
  3. När ett föremål kastas
  4. När ett objekt fångas av värde
  5. 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

Det kan ses att i en logisk kopieringskonstruktor skapas en ny dynamisk medlemsvariabel för pekaren samtidigt som värdena kopieras.

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.

Se även