SORU
13 Kasım 2010, CUMARTESİ


Üç Kuralı nedir?

Nedirbir nesneyi kopyalamademek? Nelerdirkurucu kopyalayınvekopya atama operatör? Ne zaman onlara kendimi tanıtmaya gerek var mı? Nasıl kopyalanmasını benim nesneleri engelleyebilir miyim?

CEVAP
13 Kasım 2010, CUMARTESİ


Giriş

C kullanıcı tanımlı değişkenleri türleri davranırdeğer semantiği. Bu nesneleri dolaylı olarak çeşitli bağlamlarda kopyalanır anlamına gelir ve aslında "anlamına gelir. bir nesne kopyalama ne anlamalıyız

Bize basit bir örnek verebiliriz:

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çası şaşıran, bu*. *45) denir.

Özel üye işlevleri

person bir nesneyi kopyalamak için ne anlama geliyor? main Bu fonksiyon iki farklı kopyalama senaryoları gösterir. Başlatma person b(a); tarafından yapılırkurucu kopyalayın. İşini temiz bir nesne, varolan bir nesnenin durumuna bağlı olarak oluşturmaktır. 12 ** atama ile gerçekleştirilirkopya atama operatör. İşini genellikle biraz daha karışık. hedef nesne zaten bazı geçerli durumda olduğu için bu çözülmesi gereken.

Ne kopya kurucu ne de atama operatörü (veya yıkıcı) kendimizi ilan ettik,beri bu bizim için örtülü olarak tanımlanır. Standart alıntı:

[...] Yapıcı ve kopya atama operatör kopya, [...] ve yıkıcı özel üye işlevleri. [Not:Bu uygulama örtülü bu üye fonksiyonlar ilan edecek program açık bir şekilde beyan etmez bazı sınıf türleri için. Bu uygulama örtülü olarak eğer kullanılırsa onları tanımlamak olacaktır. [...]not end] [n3126.pdf bölüm 12 §1]

Varsayılan olarak, bir nesne kopyalama üyeleri kopyalama anlamına gelir:

Örtülü olarak tanımlanan kopya sendika üyesi olmayan X bir sınıf için yapıcı subobjects bu memberwise bir kopyasını yapar. [n3126.pdf bölüm 12.8 §16]

Sendikasız sınıf X için örtülü olarak tanımlanan kopya atama operatör memberwise kopya atama yapar subobjects. [n3126.pdf bölüm 12.8 §30]

Örtük tanımlar

Örtülü olarak tanımlı person özel üye işlevleri şöyle:

// 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()
{
}

Memberwise kopyalama bu durumda istediğimiz şey tam olarak bu: name age müstakil bağımsız person bir nesne bulduk .. kopyalanır. Örtülü olarak tanımlanan yıkıcı her zaman boştur. Bu oluşturucu içinde herhangi bir kaynak elde etmedik bu yana da bu durumda gayet iyi. Üyelerin yıkıcılar person yıkıcı tamamlandıktan sonra örtülü olarak da bilinir

Yıkıcı vücut yürüttükten sonra ve herhangi bir otomatik nesneleri bünyesinde ayrılan yok, sınıf için X X doğrudan yıkıcılar [...] bir yıkıcı çağrıları üyeleri [n3126.12.4 §6] pdf

Kaynakları yönetme

Bu özel üye işlevleri açıkça ilan etmeli miyiz? Ne zaman bizim sınıfyöneten bir kaynakyani sınıfın bir nesnesi olduğundasorumlubu kaynak için. Bu genellikle kaynak olduğu anlamına gelirsatın aldıkurucu (ya da kurucu) geçtiyayınladıyıkıcı.

Bizi zamanda geriye gitmek pre-standart C için izin verin . std::string ve programcılar işaretçiler aşık gibi böyle bir şey yok. person sınıfı bu gibi görünebilir:

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 yazmak ve belaya: "Bir vektör içine bir kişi ittim ve şimdi çılgın bir bellek hatası alıyorum!" Varsayılan olarak, bir nesne kopyalama üyeleri kopyalama anlamına geldiğini unutmayın ama kopyalama name üye sadece kopya bir işaretçideğilişaret, karakter dizisi! Bu birkaç tatsız etkileri vardır:

  1. a ile değiştirir b üzerinden görülebilir.
  2. b, a.name yok edilince sarkan bir işaretçi.
  3. Eğer a yok ise, sarkan işaretçi silme undefined behavior verir.
  4. Atama name atama önce işaret,ne dikkate almaz beri er ya da geç her yerde bellek sızıntısı alacak.

Açık tanımlar

Memberwise kopyalamak istediğiniz efekti olmadığı için, kopya yapıcı ve kopya atama operatör açıkça karakter dizisi derin kopyalarını yapmak için tanımlamanız gerekir:

// 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;
}

Not başlatma ve atama arasındaki fark: name bellek sızıntıları önlemek için atamadan önce eski devleti yıkmak zorundayız. Ayrıca, form x = xkendi kendine atama karşı korumak zorundayız. Olmadan kontrol delete[] name dizi içeren silmek istiyorsunuzkaynakdize, yazarken x = x, this->name that.name her ikisi de aynı işaretçi içerir çünkü.

Özel güvenlik

Ne yazık ki, bu çözüm, eğer new char[...] bir istisna bellek tükenmesi nedeniyle atar başarısız olur. Olası bir çözüm, yerel bir değişken tanıtmak ve tabloları yeniden düzenlemek için

// 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 da açık bir kontrol olmadan, kendi kendine atama ilgilenir. Bu sorun için daha sağlam bir çözüm copy-and-swap idiom. ama özel güvenlik detaylarına burada girmeyeceğim. Ben sadece şu noktaya yapmak için özel durumlar söz:Kaynakları yönetmek sınıfları yazmak çok zor.

Noncopyable kaynakları

Bazı kaynaklar ya da kopyalanmış olmamalı, kolları ya da uyumu dosyası gibi. Bu durumda, sadece bir tanım vermeden private kopya Kurucu ve kopya atama operatör ilan:

private:

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

Alternatif olarak, boost::noncopyable miras ya da silinmiş (C 0) bildirebilirsiniz:

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

Üç kuralı

Bazen bir kaynak yöneten bir sınıf uygulamak gerekir. (Asla tek bir sınıfta birden fazla kaynakları yönetmek bu sadece ağrıya yol açabilir.) Bu durumda, unutmayınüç kuralı:

Eğer ya da açıkça yıkıcı ilan etmek gerekiyorsa, ya atama operatörü kopyasını kendinize bir kopya kurucu, muhtemelen açıkça üçü de bildirmeniz gerekir.

(Ne yazık ki, bu "" C standart veya herhangi bir derleyici farkındayım.) tarafından zorlanmaz kural

Tavsiye

Çoğu zaman, bir kaynak kendini yönetmek gerekmez çünkü std::string gibi varolan bir sınıf zaten bunu sizin için yapar. Sadece basit kodu std::string bir üyesi kullanarak karşılaştırın karmaşık ve hata eğilimli bir alternatif kullanarak char* ve ikna edilmesi gerekir. Ham işaretçi üyeleri uzak kaldığın sürece, üç kuralı kendi kodunuzu endişe pek mümkün değildir.

Bunu Paylaş:
  • Google+
  • E-Posta
Etiketler:

YORUMLAR

SPONSOR VİDEO

Rastgele Yazarlar

  • HowtoDrawAndPaint

    HowtoDrawAnd

    24 EKİM 2010
  • Joseph Hayhoe

    Joseph Hayho

    20 Mayıs 2010
  • K-391

    K-391

    23 EKİM 2012