SORU
19 Temmuz 2010, PAZARTESİ


Kopyalama ve takas deyim nedir?

Bu deyim nedir ve ne zaman kullanılmalıdır? Hangi sorunları çözüyor mu? C 11 kullanıldığında deyim değişikliği mi?

Birçok yerde sözü edilmemiş olsa da, tekil bir şeyimiz yoktu "işte burada." soru ve cevap nedir Burada daha önce bahsedildiği nerede: yerler kısmi bir listesi

CEVAP
19 Temmuz 2010, PAZARTESİ


Genel bakış

Neden kopyala ve takas deyim ihtiyacımız var mı?

Kaynak yöneten sınıf (asarıcıakıllı bir işaretçi gibi) ihtiyaçlarını The Big Three uygulamak için. Kopya-yapıcı ve yıkıcı amaçları ve uygulaması kolay olsa da, kopya atama operatör tartışmasız en incelikli ve zor. Nasıl yapmalı? Tuzaklardan Kaçınılması için ne gerekiyor?

kopyalama ve takas deyimçözüm ve zarif iki şey ulaşmada atama asist: kaçınarak 48* *ve strong exception guarantee sağlanması.

Nasıl çalışır?

Conceptually işe yarıyor kullanarak kopya-yapıcı işlev oluşturmak için yerel kopya veri, sonra alır kopyalanan veri ile swap işlevi, takas eski veriler ile yeni verileri. Geçici bir kopyasını sonra, eski veri alarak yok ediyor. Yeni verilerin bir kopyasını kalıyor.

Kullanmak için kopyalama ve takas deyim, ihtiyacımız olan üç şey vardır: bir çalışma kopya-yapıcı, yıkıcı bir çalışma (her ikisi de, temelinde herhangi bir kapsayıcı, öyle olmalı zaten tam bir swap işlevi.

Takas bir işlevinon-atmatakas üyesi için sınıf, bir üyenin iki nesne işlev. Biz olabilir cazip kullanın std::swap yerine sunmak bizim kendi, ama bunun imkansız; std::swap kullanır kopya-yapıcı ve kopya atama operatör içinde uygulanması, ve biz ... sonuçta etmeye tanımlamak atama operatörü açısından kendisi!

(Ama swap niteliksiz çağrılar bizim özel kullanacağı sadece operatör, std::swap gerektirecektir bizim sınıfın gereksiz inşaat ve yıkım üzerinden atlayarak takas.)


Ayrıntılı bir açıklama

Amaç

Hadi somut bir durum düşünün. Yoksa işe yaramaz bir sınıf olarak, dinamik bir dizi yönetmek istiyoruz. Çalışan bir kurucu, kopya-yapıcı ve yıkıcı: ile başlıyoruz

#include <algorithm> // std::copy
#include <cstddef> // std::size_t

class dumb_array
{
public:
    // (default) constructor
    dumb_array(std::size_t size = 0)
        : mSize(size),
          mArray(mSize ? new int[mSize]() : 0)
    {
    }

    // copy-constructor
    dumb_array(const dumb_array& other) 
        : mSize(other.mSize),
          mArray(mSize ? new int[mSize] : 0),
    {
        // note that this is non-throwing, because of the data
        // types being used; more attention to detail with regards
        // to exceptions must be given in a more general case, however
        std::copy(other.mArray, other.mArray   mSize, mArray);
    }

    // destructor
    ~dumb_array()
    {
        delete [] mArray;
    }

private:
    std::size_t mSize;
    int* mArray;
};

Bu sınıf neredeyse başarılı bir dizi yönetir, ama operator= düzgün çalışması gerekiyor.

Başarısız bir çözüm

Naif bir uygulama olabilir:

// the hard part
dumb_array& operator=(const dumb_array& other)
{
    if (this != &other) // (1)
    {
        // get rid of the old data...
        delete [] mArray; // (2)
        mArray = 0; // (2) *(see footnote for rationale)

        // ...and put in the new
        mSize = other.mSize; // (3)
        mArray = mSize ? new int[mSize] : 0; // (3)
        std::copy(other.mArray, other.mArray   mSize, mArray); // (3)
    }

    return *this;
} 

Ve işimiz bitti diyoruz; bu artık bir dizi, sızıntı olmadan yönetir. Ancak, üç sorunları, (n) kod sıralı olarak işaretlenmiş muzdarip.

(1) İlk atama kendi kendine test. Bu kontrol iki amaca hizmet eder: kolay bir şekilde önlemek için bizden kaçman gereksiz kod üzerinde kendi kendine atama ve korur bizden ince böcek (gibi silme dizi sadece deneyin ve kopyası). Ama tüm diğer durumlarda sadece program yavaşlatmak ve kod gürültü olarak hareket eder; kendi kendine atama nadiren oluşur, çoğu zaman bu kontrol kaybıdır. Eğer operatör olmadan düzgün bir iş olsaydı daha iyi olurdu.

(2)ikinci bir istisna bir garanti sağlar. Eğer new int[mSize] başarısız olursa, *this modifiye edilmiş olacaktır. (Yani, boyutu yanlış ve veri gitmiş!) Güçlü özel bir garanti için benzer bir şey olması gerekir:

dumb_array& operator=(const dumb_array& other)
{
    if (this != &other) // (1)
    {
        // get the new data ready before we replace the old
        std::size_t newSize = other.mSize;
        int* newArray = newSize ? new int[newSize]() : 0; // (3)
        std::copy(other.mArray, other.mArray   newSize, newArray); // (3)

        // replace the old data (all are non-throwing)
        delete [] mArray;
        mSize = newSize;
        mArray = newArray;
    }

    return *this;
} 

(3) kodu genişletti! Üçüncü soruna götürür bizi: kod çoğaltılması. Atama operatörümüz etkili zaten başka bir yerde yazdık tüm kod çiftleri, ve bu berbat bir şey.

Bizim durumumuzda, bu çekirdek sadece iki satır (ayırma ve kopya), ama daha karmaşık kaynakları ile bu kod kabartmak oldukça güçlük olabilir. Asla kendimizi tekrar etmeye gayret göstermelisiniz.

Eğer bu kadar kodu bir kaynak yönetmek için doğru, eğer benim sınıf birden fazla başarırsa ne gerekiyorsa? (bir mucize olabilir: Bu geçerli bir endişe gibi görünebilir, ve gerçekten de önemsiz olmayan try/catch maddeler gerektirir iken, bu konu dışı. Bir sınıf*!* 51) yönetmek gerekir çünkü.

Başarılı bir çözüm

Belirtildiği gibi, kopyalama ve takas deyim tüm bu sorunları çözecektir. Ama şimdi, biri dışında tüm şartları var:* *23 bir işlev. Ancak Kuralın Üç başarıyla gerektirir varlığı bizim kopya yapıcı, atama operatörü, yıkıcı, gerçekten adı "Büyük Üç Buçuk": her zaman sizin sınıf yönetir bir kaynak da mantıklı sağlamak swap işlevi.

Bizim sınıf için takas işlevselliği ekleyelim ve aşağıdaki† alırım:

class dumb_array
{
public:
    // ...

    friend void swap(dumb_array& first, dumb_array& second) // nothrow
    {
        // enable ADL (not necessary in our case, but good practice)
        using std::swap; 

        // by swapping the members of two classes,
        // the two classes are effectively swapped
        swap(first.mSize, second.mSize); 
        swap(first.mArray, second.mArray);
    }

    // ...
};

**26's, ama genel takasları daha verimli olabilir, sadece işaretçiler ve boyutlarını değiştirir ve tüm diziler tahsis kopyalamak yerine. bizim takas edebiliriz sadece şimdi değil İşlevselliği ve verimliliği bu bonus dışında, şimdi kopyalama ve takas deyim uygulamaya hazırız.

Daha fazla uzatmadan, atama operatörümüz

dumb_array& operator=(dumb_array other) // (1)
{
    swap(*this, other); // (2)

    return *this;
} 

Ve işte bu! Bir dalışta, üç sorununu zarif bir kerede ele alınıyor.

Neden işe yarıyor?

Öncelikle önemli bir seçim dikkat: parametre değişkeni alınır-değer. Bir süre kolaylıkla takip yapabilir (ve aslında, bu deyim çok naif uygulamaları):

dumb_array& operator=(const dumb_array& other)
{
    dumb_array temp(other);
    swap(*this, temp);

    return *this;
}

important optimization opportunity kaybederiz. Sadece bu değil, ama bu seçim çok önemlidir C, daha sonra açıklanan 11. (Genel bir not, son derece yararlı bir kılavuz aşağıdaki gibidir: eğer bir fonksiyon bir şey bir kopyasını yapmak için gidiyoruz, derleyici bunu parametre listesindeki edelim.‡)

Her iki şekilde de, bu yöntemin alma kaynağımız anahtar için ortadan kod çoğaltılması: biz almak için kullanın kodundan kopya-yapıcı olun kopya, ve asla ihtiyaç için tekrar herhangi bir bit. Kopyası yapıldı. takas için hazırız.

Tüm yeni veriler zaten ayrılmış, kopyalanan, ve kullanılmaya hazır fonksiyon girdikten sonra inceleyin. Bu ne kazandırır bize güçlü bir özel durum garanti için ücretsiz: biz bile vermiyor girin eğer işlevini inşaat kopyalama başarısız, ve bu nedenle mümkün alter devlet *this. (El ile daha önce yaptığımız özel durum güçlü bir garanti için, derleyici bizim için yapıyor; ne kadar nazik.)

Bu noktada swap non-atma çünkü ev yok bizim. Kopyalanan veriler ile mevcut veriler, güvenli bir şekilde devletimiz, değiştirme, takas, ve eski veri geçici içine alır. Eski verileri daha sonra işlev yapabilirler. (Nereye bağlı parametre kapsam biter ve kendi yıkıcı denir.)

Bu deyim hiçbir kod tekrarlar çünkü, operatör içinde böcek takdim edemeyiz. Bu öz-ödev kontrolü operator= tek yeknesak bir biçimde uygulanmasını sağlayarak gerek kurtulduk demektir unutmayın. (Ayrıca, biz artık özerk olmayan görevlerde performans ceza var.)

Ve bu kopyalama ve takas deyim vardır.

Ne C 11 hakkında?

C 11, Bir çok önemli kaynakları yönetmek nasıl bir değişiklik yapar C Bir sonraki sürümü , Üç Kural artıkDört Kuralı(buçuk). Neden? Yok çünkü kopyala-yapı kaynağımız, 53* *edebilmek için ihtiyacımız var.

Neyse ki bizim için, bu kolaydır:

class dumb_array
{
public:
    // ...

    // move constructor
    dumb_array(dumb_array&& other)
        : dumb_array() // initialize via default constructor, C  11 only
    {
        swap(*this, other);
    }

    // ...
};

Burada neler oluyor? Hareket-yapım amacı hatırlama: sınıfın başka bir örneği kaynak, bir devlet atanamaz olmasını güvence altına alınan ve yok edilebilir. bırakarak almak

Yani yaptıklarımız basit: başlatmak üzerinden varsayılan yapıcı (C 11 özellik), sonra takas ile other; bildiğimiz bir varsayılan örneğini inşa bizim sınıfa güvenli bir şekilde atama yapılamayan ve tahrip olduğunu biliyoruz, yani other yapmak mümkün olacak aynı, sonra takas.

(Bazı Derleyiciler kurucu heyet desteklemediğini unutmayın; bu durumda, el ile sınıfının varsayılan yükseltmeliyiz. Bu talihsiz ama neyse ki önemsiz bir görevdir.)

Neden işe yarıyor?

Bizim sınıf için yapmamız gereken tek değişiklik, o zaman neden işe yarıyor? Parametre değerleri için yapılan her önemli karar ve bir referans değil

dumb_array& operator=(dumb_array other); // (1)

Eğer other bir rvalue ile başlatılmış ise, bu durumdahareket-inşa edilecektir. Mükemmel. Aynı şekilde C 03 bize yeniden kullanalım bizim kopya yapıcı işlevleri tarafından değeri bağımsız değişken alarak, C 11otomatik olarakuygun zaman yapıcı hareket olarak seç. (Ve, tabii ki, daha önce bağlantılı makalede belirtildiği gibi, kopyalama/değer hareket sadece tamamen elided olabilir.)

Ve böylece kopyalama ve takas deyim varmıştır.


Dipnotlar

*Neden boş mArray mi göndereceğiz? Çünkü eğer herhangi bir daha fazla kod operatörü atar, yıkıcı dumb_array olabilir denir; ve eğer bu gerçekleşirse olmadan o ayar null, biz girişimi hafıza silmek için daha önceden silinmiş! Boş siliyor bir operasyon olarak null olarak ayarlamak, bu kaçınıyoruz.

†Bizim türü için std::swap uzmanlaşmış gereken diğer iddialar da var, sınıfının swap birlikte-yan serbest fonksiyonu swap, vb. bir sağlamak Ama bu tüm gereksiz: swap herhangi bir uygun kullanımı niteliksiz bir çağrı ile olacak, ve bizim fonksiyonu ADL ile bulunacaktır. Bir işlev yapacak.

Kendine kaynak sonra, takas ve/veya (C 11) taşıyabilirsiniz her yerde olması gerekiyor. nedeni ‡basittir: Ve parametre listesinde kopya yaparak, optimizasyonu en üst düzeye çıkarmak.

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

YORUMLAR

SPONSOR VİDEO

Rastgele Yazarlar

  • AceHoodVEVO

    AceHoodVEVO

    12 Mayıs 2009
  • Adam Outler

    Adam Outler

    19 EKİM 2006
  • kylediablo

    kylediablo

    8 Ocak 2007