Üçün Kuralı Nedir?


2148
  • Bir nesneyi kopyalamak ne demektir?
  • Nelerdir kopya yapıcı ve kopya atama operatörü ?
  • Onları ne zaman kendim beyan etmeliyim?
  • Nesnelerimin kopyalanmasını nasıl önleyebilirim?

52
Lütfen okumak bütün bu iplik ve etiket wiki Kapatmak oylamadan öncec++-faq .
sbi

13
@Binary: Oy vermeden önce en azından yorum tartışmasını okumak için zaman ayırın . Metin eskiden çok daha basitti, ama Fred'in üzerine genişlemesi istendi. Ayrıca, bu dört dilbilgisi sorusu olsa da, gerçekten çeşitli yönleri olan tek bir sorudur. (Eğer buna katılmıyorsanız, bu soruların her birini tek başına
cevaplayarak POV'nizi kanıtlayın

1
Fred, işte C ++ 1x ile ilgili cevabınıza ilginç bir katkı: stackoverflow.com/questions/4782757/… . Bununla nasıl başa çıkabiliriz?
sbi


4
C ++ 11'den itibaren, bunun beş kuralına veya bunun gibi bir şeye yükseltildiğini unutmayın.
paxdiablo

Yanıtlar:


1794

Giriş

C ++, kullanıcı tanımlı türlerin değişkenlerini değer semantiği ile ele alır . Bu, nesnelerin çeşitli bağlamlarda dolaylı olarak kopyalandığı ve "bir nesnenin kopyalanmasının" gerçekte ne anlama geldiğini anlamamız gerektiği anlamına gelir.

Basit bir örneği ele alalım:

class person
{
    std::string name;
    int age;

public:

    person(const std::string& name, int age) : name(name), age(age)
    {
    }
};

int main()
{
    person a("Bjarne Stroustrup", 60);
    person b(a);   // What happens here?
    b = a;         // And here?
}

( name(name), age(age)Parça tarafından şaşkınsanız , buna üye başlangıç ​​listesi denir .)

Özel üye işlevleri

Bir personnesneyi kopyalamak ne anlama geliyor ? mainFonksiyonu, iki farklı kopyalama senaryoları göstermektedir. Başlatma person b(a);, kopya oluşturucu tarafından gerçekleştirilir . Görevi, mevcut bir nesnenin durumuna göre yeni bir nesne inşa etmektir. Atama b = a, kopya atama operatörü tarafından gerçekleştirilir . İşi genellikle biraz daha karmaşıktır, çünkü hedef nesne zaten ele alınması gereken geçerli bir durumdadır.

Ne kopya kurucu ne de görevlendirme operatörü (ne de yıkıcı) kendimizi açıkladığımızdan, bunlar bizim için örtük olarak tanımlanmıştır. Standarttan alıntı:

[...] kopya oluşturucu ve kopya atama operatörü, [...] ve yıkıcı özel üye işlevleridir. [ Not : Uygulama, program açıkça belirtmediğinde bazı üye türleri için bu üye işlevlerini dolaylı olarak bildirir. Uygulama, kullanıldıklarında bunları dolaylı olarak tanımlayacaktır. [...] son not ] [n3126.pdf bölüm 12 §1]

Varsayılan olarak, bir nesneyi kopyalamak üyelerini kopyalamak anlamına gelir:

Birliğe bağlı olmayan X sınıfı için dolaylı olarak tanımlanan kopya yapıcı, alt nesnelerinin üye olarak bir kopyasını gerçekleştirir. [n3126.pdf bölüm 12.8 §16]

Birleşik olmayan X sınıfı için dolaylı olarak tanımlanmış kopya atama işleci, alt nesnelerinin üye olarak kopya atamasını gerçekleştirir. [n3126.pdf bölüm 12.8 §30]

Örtük tanımlar

Örtük olarak tanımlanmış özel üye işlevleri aşağıdaki persongibi görünür:

// 1. copy constructor
person(const person& that) : name(that.name), age(that.age)
{
}

// 2. copy assignment operator
person& operator=(const person& that)
{
    name = that.name;
    age = that.age;
    return *this;
}

// 3. destructor
~person()
{
}

Üye olarak kopyalama, bu durumda tam olarak istediğimiz şeydir: nameve agekopyalanır, böylece bağımsız, bağımsız bir personnesne elde ederiz . Örtük olarak tanımlanmış yıkıcı her zaman boştur. Bu durumda da yapıcıda herhangi bir kaynak edinmediğimiz için sorun yok. Üyelerin yıkıcıları, personyıkıcı bittikten sonra örtük olarak çağrılır :

Yıkıcının gövdesini uyguladıktan ve vücut içinde tahsis edilen herhangi bir otomatik nesneyi yok ettikten sonra, X sınıfı için bir yıkıcı, X'in doğrudan [...] üyeleri için yıkıcıları çağırır [n3126.pdf 12.4 §6]

Kaynakları yönetme

Peki bu özel üye işlevlerini ne zaman açık bir şekilde ilan etmeliyiz? Sınıfımız bir kaynağı yönettiğinde , yani sınıfın bir nesnesi bu kaynaktan sorumlu olduğunda. Bu genellikle kaynağın kurucuda edinildiği (veya kurucuya geçirildiği) ve yıkıcıda bırakıldığı anlamına gelir .

Önceden standart C ++ 'a geri dönelim. Böyle bir şey yoktu std::stringve programcılar işaretçilerle aşıktı. personSınıf bu benziyordu olabilir:

class person
{
    char* name;
    int age;

public:

    // the constructor acquires a resource:
    // in this case, dynamic memory obtained via new[]
    person(const char* the_name, int the_age)
    {
        name = new char[strlen(the_name) + 1];
        strcpy(name, the_name);
        age = the_age;
    }

    // the destructor must release this resource via delete[]
    ~person()
    {
        delete[] name;
    }
};

Bugün bile, insanlar hala bu tarzda sınıflar yazıyor ve sorun yaşıyor: " Bir kişiyi bir vektöre ittim ve şimdi çılgın bellek hataları alıyorum! " Varsayılan olarak, bir nesneyi kopyalamak üyelerini kopyalamak, ancak nameüyeyi sadece kopyalamak demektir. işaret ettiği karakter dizisini değil , bir işaretçiyi kopyalar ! Bunun birkaç hoş olmayan etkisi vardır:

  1. Yoluyla değişiklikler agözlemlenebilir b.
  2. Bir kez byok edildiğinde, a.namesarkan bir işaretçi.
  3. aYok edilirse , sarkan işaretçiyi silmek tanımlanmamış davranış sağlar .
  4. nameÖdev, ödevden önce neye işaret ettiğini dikkate almadığından , er ya da geç her yerde bellek sızıntıları alırsınız.

Açık tanımlar

Üye yönde kopyalama istenen etkiye sahip olmadığından, karakter dizisinin derin kopyalarını oluşturmak için kopyalama yapıcısını ve kopyalama atama operatörünü açıkça tanımlamalıyız:

// 1. copy constructor
person(const person& that)
{
    name = new char[strlen(that.name) + 1];
    strcpy(name, that.name);
    age = that.age;
}

// 2. copy assignment operator
person& operator=(const person& that)
{
    if (this != &that)
    {
        delete[] name;
        // This is a dangerous point in the flow of execution!
        // We have temporarily invalidated the class invariants,
        // and the next statement might throw an exception,
        // leaving the object in an invalid state :(
        name = new char[strlen(that.name) + 1];
        strcpy(name, that.name);
        age = that.age;
    }
    return *this;
}

Başlatma ve atama arasındaki farka dikkat edin: namebellek sızıntılarını önlemek için atamadan önce eski durumu yıkmalıyız. Ayrıca, formun kendi kendine atanmasına karşı da korunmalıyız x = x. O çek olmadan, delete[] nameiçeren diziyi silmek istiyorsunuz kaynak yazdığınız zaman, çünkü dize x = x, hem this->nameve that.nameaynı işaretçi içerir.

İstisna güvenliği

Ne yazık ki, new char[...]bellek tükenmesi nedeniyle bir istisna atarsa bu çözüm başarısız olacaktır . Olası bir çözüm, yerel bir değişken sunmak ve ifadeleri yeniden sıralamaktır:

// 2. copy assignment operator
person& operator=(const person& that)
{
    char* local_name = new char[strlen(that.name) + 1];
    // If the above statement throws,
    // the object is still in the same state as before.
    // None of the following statements will throw an exception :)
    strcpy(local_name, that.name);
    delete[] name;
    name = local_name;
    age = that.age;
    return *this;
}

Bu aynı zamanda açık bir denetim olmadan kendi kendine görevlendirmeyi de halleder. Bu soruna daha da sağlam bir çözüm, kopyala-takas deyimidir , ancak burada istisna güvenliği ayrıntılarına girmeyeceğim. Sadece aşağıdaki noktaya gelmek için istisnalardan bahsettim: Kaynakları yöneten sınıflar yazmak zor.

Uyumsuz kaynaklar

Dosya tanıtıcıları veya muteksler gibi bazı kaynaklar kopyalanamaz veya kopyalanmamalıdır. Bu durumda, kopya oluşturucu ve kopya atama işlecini privatebir tanım vermeden açıklayın:

private:

    person(const person& that);
    person& operator=(const person& that);

Alternatif olarak, bunları devralınabilir boost::noncopyableveya silindi olarak bildirebilirsiniz (C ++ 11 ve üstü sürümlerde):

person(const person& that) = delete;
person& operator=(const person& that) = delete;

Üç kuralı

Bazen bir kaynağı yöneten bir sınıf uygulamanız gerekir. (Asla tek bir sınıfta birden fazla kaynağı yönetmeyin, bu sadece acıya yol açacaktır.) Bu durumda, üç kuralı unutmayın :

Yıkıcı, kopya oluşturucu veya kopya atama operatörünü kendiniz açıkça bildirmeniz gerekiyorsa, muhtemelen bunların üçünü de açıkça bildirmeniz gerekir.

(Ne yazık ki, bu "kural" C ++ standardı veya bildiğim herhangi bir derleyici tarafından uygulanmıyor.)

Beş kuralı

C ++ 11'den itibaren, bir nesnenin 2 ekstra özel üye işlevi vardır: taşıma yapıcısı ve taşıma ataması. Beş devlet kuralı da bu işlevleri yerine getirmek için.

İmzalara bir örnek:

class person
{
    std::string name;
    int age;

public:
    person(const std::string& name, int age);        // Ctor
    person(const person &) = default;                // Copy Ctor
    person(person &&) noexcept = default;            // Move Ctor
    person& operator=(const person &) = default;     // Copy Assignment
    person& operator=(person &&) noexcept = default; // Move Assignment
    ~person() noexcept = default;                    // Dtor
};

Sıfır kuralı

3/5 kuralı, 0/3/5 kuralı olarak da adlandırılır. Kuralın sıfır kısmı, sınıfınızı oluştururken özel üye işlevlerinden hiçbirini yazmamanıza izin verildiğini belirtir.

Tavsiye

Çoğu zaman, bir kaynağı kendiniz yönetmeniz gerekmez, çünkü varolan bir sınıf std::stringzaten sizin için yapar. Bir std::stringüyeyi kullanarak basit kodu, a kullanarak kıvrımlı ve hataya açık alternatifle karşılaştırın char*ve ikna olmalısınız. Ham işaretçi üyelerinden uzak durduğunuz sürece, üç kuralın kendi kodunuzu ilgilendirmesi olası değildir.


4
Fred, (A) kopyalanabilir kodda kötü uygulanmış ödevi hecelemeseydin ve yanlış olduğunu söyleyen bir not eklersen ve ince baskıda başka bir yere baksaydın, yukarı oyumu daha iyi hissederdim; ya kodda c & s kullanın ya da sadece tüm bu üyeleri (B) uygulamak için atlayın ilk yarısını kısaltmak, RoT ile ilgisi az; (C) hareket semantiğinin tanıtılmasını ve bunun RoT için ne anlama geldiğini tartışırsınız.
sbi

7
Ama sonra yazı C / W yapılmalı diye düşünüyorum. Böyle çoğunlukla doğru terimleri tutmak (siz "demek olduğunu yani kopya atama operatörü", ve ortak tuzağına dokunun olmadığını atama bir kopyasını ima olamayacağını).
Johannes Schaub - litb

4
@Prasoon: Cevabın yarısını kesmek, CW olmayan bir cevabın "adil bir şekilde düzenlenmesi" olarak görülmeyeceğini düşünüyorum.
sbi


5
@solalito Kullanımdan sonra serbest bırakmanız gereken her şey: eşzamanlılık kilitleri, dosya tanıtıcıları, veritabanı bağlantıları, ağ yuvaları, yığın bellek ...
fredoverflow

509

Üç Kuralı C ++ için pratik bir kuraldır, temelde söyleyerek

Sınıfınızın şunlardan birine ihtiyacı varsa

  • bir kopya kurucu ,
  • bir atama operatörü ,
  • ya da bir yıkıcı ,

explictly tanımlanan, o zaman ihtiyaç muhtemeldir üçünü .

Bunun nedeni, üçünün de genellikle bir kaynağı yönetmek için kullanılmasıdır ve sınıfınız bir kaynağı yönetiyorsa, genellikle kopyalamanın yanı sıra serbest bırakmayı da yönetmesi gerekir.

Sınıfınızın yönettiği kaynağı kopyalamak için iyi bir anlam bilgisi yoksa , kopya oluşturucu ve atama işlecini olarak bildirerek ( tanımlamamak ) kopyalamayı yasaklamayı düşünün private.

(C ++ standardının yakında çıkacak olan yeni sürümünün (C ++ 11 olan) C ++ 'a hareket semantiği eklediğini ve bu da Üçün Kuralını değiştireceğini unutmayın. Bununla birlikte, bir C ++ 11 bölümü yazmak için çok az şey biliyorum Üç Kuralı hakkında.)


3
Kopyalamayı önlemek için başka bir çözüm, kopyalanamayan (gibi boost::noncopyable) bir sınıftan miras (özel olarak) almaktır . Ayrıca çok daha net olabilir. Ben C ++ 0x ve "silme" fonksiyonları burada yardımcı olabilir, ancak sözdizimini unuttum düşünüyorum: /
Matthieu M.

2
@ Matthieu: Evet, bu da işe yarıyor. Ama noncopyablestd lib'in bir parçası olmadığı sürece , bunu bir gelişme olarak görmüyorum. (Oh, ve silme sözdizimini unuttuysan, daha önce bildiğim mor :)
ethan'ı unuttun

3
@Daan: Bu cevaba bakınız . Ancak, ben sadık öneriyoruz Martinho 'ın Zero Kuralı . Bana göre, bu, son on yılda üretilen C ++ için en önemli kurallardan biridir.
sbi

3
Martinho'nun Sıfırlama Kuralı artık daha iyi (belirgin reklam yazılımı devralma olmadan) archive.org'da
Nathan Kidd

161

Büyük üçün yasası yukarıda belirtildiği gibidir.

Basit bir İngilizcede çözdüğü problemin kolay bir örneği:

Varsayılan olmayan yıkıcı

Yapıcıya bellek ayırdınız ve bu yüzden silmek için bir yıkıcı yazmanız gerekir. Aksi takdirde bellek sızıntısına neden olursunuz.

Bunun bir iş olduğunu düşünebilirsiniz.

Sorun, nesnenizin bir kopyası yapılmışsa, kopya orijinal nesneyle aynı belleği gösterecektir.

Bunlardan biri yıkıcıdaki hafızayı siler, diğeri geçersiz hafızaya bir işaretçi (buna sarkan bir işaretçi denir) kullanmaya başladığında işler kıllı olur.

Bu nedenle, yeni nesnelerin yok etmek için kendi bellek parçalarını ayırması için bir kopya oluşturucu yazarsınız.

Atama operatörü ve kopya oluşturucu

Oluşturucunuzdaki belleği sınıfınızın üye işaretçisine ayırdınız. Bu sınıftaki bir nesneyi kopyaladığınızda, varsayılan atama işleci ve kopya oluşturucu bu üye işaretçisinin değerini yeni nesneye kopyalar.

Bu, yeni nesnenin ve eski nesnenin aynı bellek parçasını göstereceği anlamına gelir; böylece bir nesnede değiştirdiğinizde, diğer objektif için de değiştirilir. Bir nesne bu hafızayı silerse, diğeri onu kullanmaya çalışacaktır - eek.

Bu sorunu çözmek için, kopya oluşturucu ve atama işlecinin kendi sürümünü yazarsınız. Sürümleriniz yeni nesnelere ayrı bellek ayırır ve adresinden ziyade ilk işaretçinin işaret ettiği değerlere kopyalar.


4
Bu nedenle, bir kopya oluşturucu kullanırsak, kopya yapılır, ancak tamamen farklı bir bellek konumunda ve kopya oluşturucu kullanmazsak, kopya yapılır, ancak aynı bellek konumuna işaret eder. söylemeye çalıştığın şey bu mu? Bu nedenle, kopya oluşturucu içermeyen bir kopya, yeni bir işaretçinin orada olacağı anlamına gelir, ancak aynı bellek konumunu işaret eder, ancak kullanıcı tarafından açıkça tanımlanmış bir kopya oluşturucumuz varsa, farklı bir bellek konumuna işaret eden ancak verilere sahip ayrı bir işaretçimiz olacaktır.
Kırılmaz

4
Üzgünüz, bu çağlara önce cevap verdim ama cevabım hala burada görünmüyor :-( Temel olarak, evet - anladın :-)
Stefan

1
Prensip, kopya atama operatörüne nasıl etki eder? Bu kural, Üçüncül Kuraldaki 3. maddeden bahsedilirse daha yararlı olacaktır.
DBedrenko

1
@DBedrenko, "yeni nesnelere kendi bellek parçalarını ayırması için bir kopya oluşturucu yazıyorsunuz ..." Bu, kopya atama operatörüne uzanan ilkeyle aynı. Bunu netleştirdiğimi düşünmüyor musun?
Stefan

2
@DBedrenko, biraz daha bilgi ekledim. Bu daha net mi?
Stefan

44

Temel olarak, bir yıkıcı (varsayılan yıkıcı değil) varsa, tanımladığınız sınıfın bir miktar bellek ayırması olduğu anlamına gelir. Sınıfın dışarıda bazı istemci kodları veya sizin tarafınızdan kullanıldığını varsayalım.

    MyClass x(a, b);
    MyClass y(c, d);
    x = y; // This is a shallow copy if assignment operator is not provided

MyClass yalnızca bazı ilkel yazılan üyelere sahipse, varsayılan atama işleci çalışır, ancak bazı işaretçi üyeleri ve atama işleçleri olmayan nesneleri varsa, sonuç tahmin edilemez olur. Bu nedenle, bir sınıfın yıkıcısında silinecek bir şey varsa, derin bir kopya operatörüne ihtiyacımız olabileceğini söyleyebiliriz, bu da bir kopya oluşturucu ve atama operatörü sağlamamız gerektiği anlamına gelir.


36

Bir nesneyi kopyalamak ne demektir? Nesneleri kopyalamanın birkaç yolu vardır - en çok bahsettiğiniz 2 çeşit hakkında konuşalım - derin kopya ve sığ kopya.

Nesneye yönelik bir dilde olduğumuzdan (veya en azından öyle olduğunu varsaydığımızdan), diyelim ki bir bellek ayırdınız. Bir OO dili olduğundan, ayırdığımız bellek parçalarına kolayca başvurabiliriz çünkü bunlar genellikle ilkel değişkenler (ints, chars, bayt) veya kendi tiplerimiz ve ilkellerden yapılmış tanımladığımız sınıflardır. Diyelim ki şöyle bir Araba sınıfımız var:

class Car //A very simple class just to demonstrate what these definitions mean.
//It's pseudocode C++/Javaish, I assume strings do not need to be allocated.
{
private String sPrintColor;
private String sModel;
private String sMake;

public changePaint(String newColor)
{
   this.sPrintColor = newColor;
}

public Car(String model, String make, String color) //Constructor
{
   this.sPrintColor = color;
   this.sModel = model;
   this.sMake = make;
}

public ~Car() //Destructor
{
//Because we did not create any custom types, we aren't adding more code.
//Anytime your object goes out of scope / program collects garbage / etc. this guy gets called + all other related destructors.
//Since we did not use anything but strings, we have nothing additional to handle.
//The assumption is being made that the 3 strings will be handled by string's destructor and that it is being called automatically--if this were not the case you would need to do it here.
}

public Car(const Car &other) // Copy Constructor
{
   this.sPrintColor = other.sPrintColor;
   this.sModel = other.sModel;
   this.sMake = other.sMake;
}
public Car &operator =(const Car &other) // Assignment Operator
{
   if(this != &other)
   {
      this.sPrintColor = other.sPrintColor;
      this.sModel = other.sModel;
      this.sMake = other.sMake;
   }
   return *this;
}

}

Derin bir kopya, bir nesneyi bildirip sonra nesnenin tamamen ayrı bir kopyasını oluşturmamızdır ... 2 tamamen bellek kümesinde 2 nesne ile sonuçlanırız.

Car car1 = new Car("mustang", "ford", "red");
Car car2 = car1; //Call the copy constructor
car2.changePaint("green");
//car2 is now green but car1 is still red.

Şimdi garip bir şey yapalım. Diyelim ki car2 yanlış programlanmış ya da bilerek car1'in yapıldığı gerçek belleği paylaşmak içindir. (Bunu yapmak genellikle bir hatadır ve sınıflarda genellikle tartışıldığı battaniyedir.) Car2 hakkında her soru sorduğunuzda, car1'in bellek alanına gerçekten bir işaretçi çözdüğünüzü varsayalım ... dır-dir.

//Shallow copy example
//Assume we're in C++ because it's standard behavior is to shallow copy objects if you do not have a constructor written for an operation.
//Now let's assume I do not have any code for the assignment or copy operations like I do above...with those now gone, C++ will use the default.

 Car car1 = new Car("ford", "mustang", "red"); 
 Car car2 = car1; 
 car2.changePaint("green");//car1 is also now green 
 delete car2;/*I get rid of my car which is also really your car...I told C++ to resolve 
 the address of where car2 exists and delete the memory...which is also
 the memory associated with your car.*/
 car1.changePaint("red");/*program will likely crash because this area is
 no longer allocated to the program.*/

Hangi dilde yazdığınızdan bağımsız olarak, nesneleri kopyalama konusunda ne demek istediğiniz konusunda çok dikkatli olun çünkü çoğu zaman derin bir kopya istiyorsunuz.

Kopya oluşturucu ve kopya atama operatörü nedir? Onları yukarıda zaten kullandım. Kopya yapıcı aşağıdaki gibi kod yazdığınızda denir Car car2 = car1; Eğer bir değişken tanımlayın ve tek satırda atarsanız kopya yapıcısı çağrıldığında olduğunu, Esasen. Atama işleci, eşittir işaretini kullandığınızda olan şeydir car2 = car1;. Uyarı car2aynı açıklamada bildirilmedi. Bu işlemler için yazdığınız iki kod parçası büyük olasılıkla çok benzerdir. Aslında, tipik tasarım deseninin, ilk kopya / atamanın meşru olduğunu düşündüğünüzde her şeyi ayarlamak için çağırdığınız başka bir işlevi vardır - yazdığım uzun el koduna bakarsanız, işlevler hemen hemen aynıdır.

Onları ne zaman kendim beyan etmeliyim? Paylaşılacak veya bir şekilde üretim için kod yazmıyorsanız, gerçekten sadece ihtiyacınız olduğunda bildirmeniz gerekir. Eğer 'kazara' kullanmayı tercih ederseniz ve dil yapmazsanız, program dilinizin ne yaptığının farkında olmanız gerekir - yani derleyicinin varsayılanını alırsınız. Örneğin nadiren kopya kurucuları kullanıyorum, ancak atama operatörü geçersiz kılmaları çok yaygın. Toplama, çıkarma vb.'nin de anlamını geçersiz kılabileceğinizi biliyor muydunuz?

Nesnelerimin kopyalanmasını nasıl önleyebilirim? Özel bir işlevle nesneniz için bellek ayırmanıza izin verilen tüm yolları geçersiz kılmak makul bir başlangıçtır. İnsanların onları kopyalamasını gerçekten istemiyorsanız, bir istisna atarak ve aynı zamanda nesneyi kopyalayarak programı herkese açık hale getirebilir ve programcıyı uyarabilirsiniz.


5
Soru C ++ olarak etiketlendi. Bu sözde kod sergisi, iyi tanımlanmış "Üçüncül Kural" hakkında hiçbir şeyi açıklığa kavuşturmak için çok az şey yapar ve karışıklığı en kötü şekilde yayar.
14'te

26

Onları ne zaman kendim beyan etmeliyim?

Üçüncül Kural,

  1. kopya oluşturucu
  2. atama işleci kopyala
  3. çöp yakma fırını

o zaman üçünü de ilan etmelisiniz. Bir kopyalama işleminin anlamını devralma ihtiyacının neredeyse her zaman bir çeşit kaynak yönetimi gerçekleştiren sınıftan kaynaklandığı ve neredeyse her zaman

  • bir kopyalama işleminde yapılan kaynak yönetimi ne olursa olsun, diğer kopyalama işleminde yapılması gerekiyordu ve

  • sınıf yıkıcısı da kaynağın yönetimine katılırdı (genellikle onu serbest bırakır). Yönetilecek klasik kaynak bellekti ve bu nedenle belleği yöneten tüm Standart Kütüphane sınıflarının (örn. Dinamik bellek yönetimi yapan STL kapsayıcıları) hepsi “büyük üç” bildiriyor: hem kopyalama işlemleri hem de bir yıkıcı.

Üç Kuralı'nın bir sonucu, kullanıcı tarafından bildirilen bir yıkıcının varlığının, basit üye akıllı kopyasının, sınıftaki kopyalama işlemleri için uygun olmadığını göstermesidir. Bu da, eğer bir sınıf bir yıkıcı ilan ederse, kopyalama işlemlerinin muhtemelen otomatik olarak üretilmemesi gerektiğini, çünkü doğru olanı yapmayacaklarını gösterir. C ++ 98 kabul edildiğinde, bu akıl yürütme çizgisinin önemi tam olarak takdir edilmedi, bu nedenle C ++ 98'de, kullanıcının bildirdiği bir yıkıcı varlığının, derleyicilerin kopya işlemleri oluşturma isteği üzerinde hiçbir etkisi yoktu. Bu, C ++ 11'de geçerli olmaya devam eder, ancak yalnızca kopyalama işlemlerinin oluşturulduğu koşulların kısıtlanması çok eski kodu kıracağı için.

Nesnelerimin kopyalanmasını nasıl önleyebilirim?

Kopya oluşturucu ve kopya atama işlecini özel erişim belirteci olarak bildirin.

class MemoryBlock
{
public:

//code here

private:
MemoryBlock(const MemoryBlock& other)
{
   cout<<"copy constructor"<<endl;
}

// Copy assignment operator.
MemoryBlock& operator=(const MemoryBlock& other)
{
 return *this;
}
};

int main()
{
   MemoryBlock a;
   MemoryBlock b(a);
}

C ++ 11 ve sonrasında kopya oluşturucu ve atama operatörünün silindiğini bildirebilirsiniz

class MemoryBlock
{
public:
MemoryBlock(const MemoryBlock& other) = delete

// Copy assignment operator.
MemoryBlock& operator=(const MemoryBlock& other) =delete
};


int main()
{
   MemoryBlock a;
   MemoryBlock b(a);
}


10

C ++ 'da üç kural, aşağıdaki üye işlevlerden birinde açık bir tanım varsa, programcının diğer iki üye işlevlerini birlikte tanımlaması gereken üç gereksinimin tasarım ve temel ilkesidir. Yani şu üç üye işlevi vazgeçilmezdir: yıkıcı, kopya oluşturucu, kopya atama operatörü.

C ++ 'da kopya kurucu özel bir kurucudur. Varolan bir nesnenin kopyasına eşdeğer olan yeni nesne olan yeni bir nesne oluşturmak için kullanılır.

Kopya atama işleci, genellikle varolan bir nesneyi aynı nesne türüne sahip başkalarına belirtmek için kullanılan özel bir atama işlecidir.

Hızlı örnekler var:

// default constructor
My_Class a;

// copy constructor
My_Class b(a);

// copy constructor
My_Class c = a;

// copy assignment operator
b = a;

7
Merhaba, cevabınız yeni bir şey eklemiyor. Diğerleri konuyu çok daha derinliklerde ve daha doğru bir şekilde ele alır - cevabınız bazı yerlerde yaklaşık ve aslında yanlıştır (yani burada "zorunluluk" yoktur; "çok büyük olasılıkla olmalıdır"). Zaten iyice cevaplanmış sorulara bu tür bir cevap gönderirken gerçekten değmez. Ekleyeceğiniz yeni şeyler olmadığı sürece.
Mat

1
Ayrıca, orada dört olan küçük örnek, her nasılsa ilişkin iki arasında üç Üçlü Kural bahsediyor söyledi. Çok fazla kafa karışıklığı.
anatolyg
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.