Kopyala ve takas deyimi nedir?


2001

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

Her ne kadar birçok yerde bahsedilmiş olsa da, herhangi bir tek "ne" sorusu ve cevabı yoktu, işte burada. Daha önce bahsedildiği yerlerin kısmi bir listesi:




4
Bu deyim için tam bir açıklama yapmak iyi bir fikir, herkesin bilmesi gereken çok yaygın.
Matthieu M.Temmuz

16
Uyarı: Kopyalama / değiştirme deyimi yararlı olduğundan çok daha sık kullanılır. Kopya atamadan güçlü bir istisna güvenlik garantisi gerekmediğinde performans için genellikle zararlıdır. Kopya atama için güçlü istisna güvenliği gerektiğinde, çok daha hızlı bir kopya atama operatörüne ek olarak kısa bir genel işlevle kolayca sağlanır. Bkz. Slideshare.net/ripplelabs/howard-hinnant-accu2014 43 - 53. Slaytlar. Özet: copy / swap araç kutusundaki kullanışlı bir araçtır. Ancak aşırı pazarlandı ve daha sonra sıklıkla istismar edildi.
Howard Hinnant

2
@HowardHinnant: Evet, ona +1. Bunu hemen hemen her C ++ sorusunun "bir kopyasını oluştururken sınıfımın çökmesine yardım" olduğu bir zamanda yazdım ve bu benim yanıtımdı. Sadece kopyala / taşı-anlambilim veya başka şeylere geçebilmek istediğiniz her şey için uygundur, ancak bu gerçekten optimal değildir. Bunun yardımcı olacağını düşünüyorsanız cevabımın üstüne bir feragatname koymaktan çekinmeyin.
GManNickG

Yanıtlar:


2184

genel bakış

Kopyalama ve değiştirme deyimine neden ihtiyacımız var?

Bir kaynağı ( akıllı bir işaretçi gibi bir sarıcı) yöneten herhangi bir sınıfın Büyük Üç'ü uygulaması gerekir . Kopya oluşturucu ve yıkıcıların hedefleri ve uygulaması açık olsa da, kopya atama operatörü tartışmasız en nüanslı ve zordur. Nasıl yapılmalı? Hangi tuzaklardan kaçınılmalıdır?

Kopyala-takas deyim çözümdür ve zarif iki şey ulaşmada atama operatöre yardımcı olur: kaçınarak kod çoğaltma ve sağlayan güçlü istisna garanti .

O nasıl çalışır?

Kavramsal olarak , verilerin yerel bir kopyasını oluşturmak için kopya oluşturucu işlevini kullanarak çalışır, daha sonra kopyalanan verileri bir swapişlevle alır ve eski verileri yeni verilerle değiştirir. Geçici kopya daha sonra eski verileri beraberinde alarak yok eder. Yeni verilerin bir kopyası bırakıldı.

Kopyala-takas deyimini kullanmak için üç şeye ihtiyacımız var: çalışan bir kopya oluşturucu, çalışan bir yıkıcı (her ikisi de herhangi bir sargının temelidir, bu yüzden zaten tamamlanmalıdır) ve bir swapişlev.

Takas işlevi , üyeye üye olan bir sınıfın iki nesnesini değiştiren fırlatmayan bir fonksiyondur. Kendimizi std::swapsağlamak yerine kullanmaya cazip gelebiliriz , ama bu imkansız olurdu; std::swapkopya oluşturucu ve kopya atama işlecini uygulaması içinde kullanır ve sonuçta atama işlecini kendisi açısından tanımlamaya çalışırız!

(Sadece bu değil, aynı zamanda kalifiye olmayan çağrılar, swapözel takas operatörünüzü kullanacak ve sınıfımızın gereksiz inşaat ve yıkımını std::swapatlayacaktır.)


Ayrıntılı bir açıklama

Amaç

Somut bir durumu ele alalım. Aksi halde işe yaramaz bir sınıfta dinamik bir dizi yönetmek istiyoruz. Çalışan bir kurucu, kopya kurucu 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]() : nullptr)
    {
    }

    // copy-constructor
    dumb_array(const dumb_array& other)
        : mSize(other.mSize),
          mArray(mSize ? new int[mSize] : nullptr),
    {
        // 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 diziyi başarıyla yönetir, ancak operator=düzgün çalışması gerekir .

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

Saf bir uygulama şöyle görünebilir:

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

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

    return *this;
}

Ve biz bittiğimizi söylüyoruz; bu artık bir diziyi sızıntı olmadan yönetiyor. Bununla birlikte, kodda sırayla işaretlenen üç sorundan muzdariptir (n).

  1. Birincisi, kendini atama testidir. Bu kontrol iki amaca hizmet eder: kendi kendine atamada gereksiz kod çalıştırmamızı önlemenin kolay bir yoludur ve bizi küçük hatalardan korur (diziyi yalnızca denemek ve kopyalamak için silmek gibi). Ancak diğer tüm durumlarda, yalnızca programı yavaşlatmaya ve kodda gürültü görevi görmeye hizmet eder; kendi kendine atama nadiren gerçekleşir, bu nedenle bu kontrol çoğu zaman bir israftır. Operatörün onsuz düzgün çalışabilmesi daha iyi olurdu.

  2. İkincisi, sadece temel bir istisna garantisi sağlamasıdır. Eğer new int[mSize]başarısız *thisdeğiştirilmiş olacaktır. (Yani, boyut yanlış ve veriler kayboldu!) Güçlü bir istisna garantisi için aşağıdakine 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]() : nullptr; // (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. Kod genişledi! Bu da bizi üçüncü soruna götürür: kod çoğaltma. Atama işlecimiz, başka bir yerde yazmış olduğumuz tüm kodları etkili bir şekilde çoğaltır ve bu korkunç bir şeydir.

Bizim durumumuzda, çekirdeği sadece iki satırdır (tahsis ve kopya), ancak daha karmaşık kaynaklarla bu kod şişmesi oldukça zor olabilir. Kendimizi asla tekrar etmemeye çalışmalıyız.

(Biri merak edebilir: bir kaynağı doğru bir şekilde yönetmek için bu kadar kod gerekiyorsa, ya sınıfım birden fazla yönetiyorsa? Bu geçerli bir endişe gibi görünse de, gerçekten önemsiz try/ catchcümle gerektiriyorsa , bu bir Çünkü bir sınıf sadece bir kaynağı yönetmelidir !)

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

Belirtildiği gibi, kopyala-takas deyimi tüm bu sorunları çözecektir. Ama şu anda, biri hariç tüm gereksinimlere sahibiz: bir swapişlev. Üçüncül Kural, kopya oluşturucu, atama operatörümüz ve yıkıcımızın varlığını başarıyla gerektirse de, buna gerçekten "Büyük Üç Buçuk" denmelidir: sınıfınız bir kaynağı yönettiğinde, bir swapişlev sağlamak da mantıklıdır .

Sınıfımıza takas işlevselliği eklememiz gerekiyor ve bunu aşağıdaki gibi yapıyoruz †:

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 objects,
        // the two objects are effectively swapped
        swap(first.mSize, second.mSize);
        swap(first.mArray, second.mArray);
    }

    // ...
};

( İşte neden açıklaması public friend swap.) Şimdi sadece bizimkileri dumb_arraydeğil, aynı zamanda genel olarak takaslar daha verimli olabilir; tüm dizileri ayırmak ve kopyalamak yerine yalnızca işaretçileri ve boyutları değiştirir. İşlevsellik ve verimlilikteki bu bonusun yanı sıra, artık kopyala ve takas deyimini 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 bu kadar! Biri düştüğünde, her üç problem de aynı anda zarif bir şekilde ele alınır.

Neden çalışıyor?

İlk önce önemli bir seçim olduğunu fark ediyoruz: parametre argümanı by-value alınır . Biri aşağıdakileri kolayca yapabilirken (ve aslında deyimin birçok naif uygulaması bunu yapabilir):

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

    return *this;
}

Önemli bir optimizasyon fırsatını kaybediyoruz . Sadece bu değil, bu seçenek daha sonra tartışılacak olan C ++ 11'de kritik öneme sahiptir. (Genel bir notta, son derece yararlı bir kılavuz şöyledir: bir işlevdeki bir şeyin kopyasını yapacaksanız, derleyicinin parametre listesinde yapmasına izin verin. ‡)

Her iki durumda da, kaynağımızı elde etmenin bu yöntemi kod çoğaltmayı ortadan kaldırmanın anahtarıdır: kopyayı yapmak için kodu kopya oluşturucudan kullanıyoruz ve hiçbir zaman tekrarlamamıza gerek yok. Artık kopya yapıldığı için değiştirmeye hazırız.

Fonksiyona girdikten sonra tüm yeni verilerin önceden tahsis edildiğini, kopyalandığını ve kullanıma hazır olduğunu gözlemleyin. Bize güçlü bir istisna garantisi veren şey budur: kopyanın yapımı başarısız olursa işleve bile girmeyiz ve bu nedenle durumunu değiştirmek mümkün değildir *this. (Güçlü bir istisna garantisi için daha önce manuel olarak yaptığımız şey, derleyici şimdi bizim için yapıyor; nasıl tür.)

Bu noktada evde özgürüz, çünkü swapfırlatmayan. Mevcut verilerimizi kopyalanan verilerle değiştiririz, güvenli bir şekilde durumumuzu değiştiririz ve eski veriler geçici hale getirilir. İşlev geri döndüğünde eski veriler serbest bırakılır. (Parametrenin kapsamı biter ve yıkıcısı çağrılır.)

Deyim hiçbir kodu tekrarlamadığından, operatöre hatalar ekleyemeyiz. Bunun, kendi kendine atama kontrolüne ihtiyaç duyduğumuz anlamına geldiğini ve tek bir tek tip uygulamaya izin verdiğimizi unutmayın operator=. (Buna ek olarak, artık kendi hesabına atamalarda performans cezası da yok.)

Ve bu kopyala-takas deyimidir.

C ++ 11 ne olacak?

C ++ 'ın bir sonraki sürümü olan C ++ 11, kaynakları yönetme şeklimizde çok önemli bir değişiklik yapar: Üç Kural şimdi Dört Kural (bir buçuk). Neden? Çünkü sadece kaynağımızı kopyalayabilmemiz gerekmiyor, aynı zamanda onu da taşımamız gerekiyor .

Neyse ki bizim için bu kolay:

class dumb_array
{
public:
    // ...

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

    // ...
};

Burada neler oluyor? Taşımacılık-inşaatın amacını hatırlayın: kaynakları sınıfın başka bir örneğinden almak, tahsis edilebilir ve yıkılabilir olması garanti edilen bir durumda bırakmak.

Yaptığımız şey basit: varsayılan kurucu (C ++ 11 özelliği) ile başlangıç ​​durumuna getirin, sonra ile değiştirin other; sınıfımızın varsayılan olarak oluşturulmuş bir örneğinin güvenli bir şekilde atanıp yok otheredilebileceğini biliyoruz, bu yüzden takas ettikten sonra da aynısını yapabileceğimizi biliyoruz .

(Bazı derleyicilerin kurucu temsilcisini desteklemediğini unutmayın; bu durumda sınıfı el ile varsayılan olarak yapılandırmamız gerekir. Bu talihsiz ama neyse ki önemsiz bir görevdir.)

Neden işe yarıyor?

Sınıfımızda yapmamız gereken tek değişiklik bu, neden işe yarıyor? Parametreyi referans değil, değer yapmak için verdiğimiz önemli kararı hatırlayın:

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

Şimdi, otherbir rvalue ile başlatılırsa, bu hareket inşa edilecektir . Mükemmel. Aynı şekilde C ++ 03, argüman değer değeri alarak kopya oluşturucu işlevimizi yeniden kullanmamıza izin verir, C ++ 11 uygun olduğunda otomatik olarak hareket yapıcısını seçer. (Ve elbette, daha önce bağlantılı makalede belirtildiği gibi, değerin kopyalanması / taşınması tamamen birlikte yapılabilir.)

Ve böylece kopyala-takas deyimini bitirir.


Dipnotlar

* Neden mArraynull değerine ayarlıyoruz ? Çünkü operatörde başka bir kod atarsa, yıkıcısı dumb_arrayçağrılabilir; ve bu durum null değerine ayarlanmadan gerçekleşirse, zaten silinmiş olan belleği silmeye çalışırız! Null değerini silmek işlem olmadığından, bunu null değerine ayarlayarak bundan kaçınırız.

† Tipimiz std::swapiçin uzmanlaşmamız , sınıf içi swapbir serbest fonksiyon swap, vb. Sağlamamız gerektiğine dair başka iddialar swapda var. ADL aracılığıyla bulundu . Bir işlev yapacak.

‡ Sebebi basit: Bir kez kaynağınız varsa, onu değiştirebilir ve / veya olması gereken yere taşıyabilirsiniz (C ++ 11). Ve parametre listesinde kopya yaparak optimizasyonu en üst düzeye çıkarırsınız.

†→ Hareket yapıcı genellikle olmalıdır noexcept, aksi takdirde bazı kodlar (örn. Yeniden std::vectorboyutlandırma mantığı) bir hareket mantıklı olsa bile kopya yapıcısını kullanır. Tabii ki, sadece içindeki kod istisnalar atmıyorsa bunu işaretleyin.


17
@GMan: Bir kerede birkaç kaynağı yöneten bir sınıfın başarısız olmaya mahkum olduğunu iddia ederim (istisna güvenliği kabus haline gelir) ya da bir sınıfın bir kaynağı yönetmesini VEYA iş işlevselliği ve kullanım yöneticileri olmasını şiddetle tavsiye ederim.
Matthieu M.Temmuz

22
Takas yönteminin neden arkadaş olarak ilan edildiğini anlamıyorum?
szx

9
@asd: ADL aracılığıyla bulunmasına izin vermek için.
GManNickG

8
@neuviemeporte: Parantez ile, diziler öğeleri varsayılan olarak başlatılır. Olmadan, başlatılmazlar. Kopya oluşturucuda yine de değerlerin üzerine yazacağımızdan, başlatmayı atlayabiliriz.
GManNickG

10
@neuviemeporte: Karşılaşacağınız swapen genel kodda, örneğin boost::swapve diğer çeşitli takas örneklerinde çalışmasını istiyorsanız, ADL sırasında bulunmanız gerekir . Takas, C ++ 'da zor bir konudur ve genellikle hepimiz tek bir erişim noktasının (tutarlılık için) en iyisi olduğu konusunda hemfikiriz ve bunu yapmanın tek yolu genel olarak ücretsiz bir işlevdir ( inttakas üyesi olamaz, Örneğin). Arka plan için soruma bakın .
GManNickG

274

: Atama, özünde, iki adım nesnenin eski halini yıkmak ve kopya olarak yeni bir toplum inşa diğer bazı nesnenin devletin.

Temel olarak, yıkıcı ve kopya oluşturucu bunu yapar, bu yüzden ilk fikir işi onlara devretmek olacaktır. Bununla birlikte, yıkım başarısız olmamalı, inşaat olsa da, aslında bunu başka bir şekilde yapmak istiyoruz : önce yapıcı parçayı gerçekleştirin ve eğer başarılı olursa yıkıcı parçayı yapın . Kopyala-takas deyimi, bunu yapmanın bir yoludur: Önce geçici bir nesne oluşturmak için bir sınıfın kopya yapıcısını çağırır, ardından verilerini geçici olanlarla değiştirir ve sonra geçici yıkıcıya eski durumu yok etmesini sağlar.
Dan beriswap()hiçbir zaman başarısız olmaz, başarısız olabilecek tek bölüm kopya yapımıdır. Bu önce yapılır ve başarısız olursa, hedeflenen nesnede hiçbir şey değişmez.

Rafine edilmiş formunda, kopyalama ve takas, atama operatörünün (referans olmayan) parametresi başlatılarak kopyalamanın gerçekleştirilmesiyle gerçekleştirilir:

T& operator=(T tmp)
{
    this->swap(tmp);
    return *this;
}

1
Bence pimpl'den bahsetmek kopyadan, takastan ve yıkımdan bahsetmek kadar önemli. Takas sihirli bir şekilde istisna açısından güvenli değildir. İstisna güvenlidir, çünkü işaretçileri değiştirmek istisna güvenlidir. Sen yok olması bir Pimpl kullanmak, ancak bunu yapmazsanız o zaman emin bir üyesi her takas istisna-güvenli olduğunu yapmalıdır. Bu üyelerin değişebileceği bir kabus olabilir ve bir sivilce gizlendiklerinde önemsizdir. Ve sonra, sivilce maliyeti geliyor. Bu da bizi istisna güvenliğinin performansta bir maliyet taşıdığı sonucuna götürüyor.
wilhelmtell

7
std::swap(this_string, that)atışsız garanti vermez. Güçlü istisna güvenliği sağlar, ancak atışsız garanti vermez.
wilhelmtell

11
@wilhelmtell: C ++ 03'te potansiyel olarak atılan std::string::swap(çağrılan std::swap) istisnalardan bahsedilmez . C ++ 0 x yılında std::string::swapise noexceptve istisnalar atmak etmemelidir.
James McNellis

2
@sbi @JamesMcNellis tamam, ama nokta hala duruyor: sınıf türünde üyeleriniz varsa, bunları değiştirmenin bir atım olmadığından emin olmalısınız. İşaretçi olan tek bir üyeniz varsa bu önemsizdir. Aksi halde değil.
wilhelmtell

2
@wilhelmtell: Bunun takas noktası olduğunu düşündüm: asla atmıyor ve her zaman O (1) (evet, biliyorum, std::array...)
sbi

44

Şimdiden bazı iyi cevaplar var. Ben esas olarak onlar eksik düşünüyorum - kopyala-takas deyim ile "eksilerini" bir açıklama odaklanmak ....

Kopyala ve takas deyimi nedir?

Atama operatörünü takas işlevi açısından uygulamanın bir yolu:

X& operator=(X rhs)
{
    swap(rhs);
    return *this;
}

Temel fikir şudur:

  • bir nesneye atamanın en hataya açık kısmı, yeni durumun ihtiyaç duyduğu kaynakları elde etmektir (örn. bellek, tanımlayıcılar)

  • eğer yeni değerin bir kopyası yapılırsa , nesnenin mevcut durumu değiştirilmeden önce elde edilebilir (yani *this), bu yüzden referans ile değil de değerle (yani kopyalanır) rhskabul edilir .

  • yerel kopya durumunu takas rhsve *thisolduğu genellikle lokal kopyası daha sonra herhangi bir devlete ihtiyacımız yok verilen potansiyel başarısızlık / istisna olmadan nispeten kolay yapmak (sadece çok olan bir nesne gibi, kaçak için yıkıcı için devlet uyum ihtiyacı taşındı itibaren> = C ++ 11)

Ne zaman kullanılmalıdır? (Hangi sorunları çözer [/ create] ?)

  • İstendiğinde, itiraz edilmesini istediğinizde, bir istisna fırlatan bir görevden etkilenmeden swap, güçlü bir istisna garantisine sahip olduğunuzu veya yazabileceğinizi ve ideal olarak başarısız olamayacağınızı varsayarak throw.. .. †

  • Atama işlecini (daha basit) kopya oluşturucu swapve yıkıcı işlevleri açısından tanımlamak için temiz, anlaşılması kolay, sağlam bir yol istediğinizde .

    • Kopyalama ve takas olarak yapılan kendinden atama, gözden kaçan kenar durumlarını önler.

  • Ödev sırasında fazladan geçici bir nesneye sahip olarak yaratılan herhangi bir performans cezası veya geçici olarak daha yüksek kaynak kullanımı başvurunuz için önemli olmadığında. ⁂

swapatma: genellikle nesnelerin işaretçiye göre izlediği veri üyelerini güvenilir bir şekilde takas etmek, ancak fırlatmasız bir takası olmayan veya takas olarak uygulanacak olan X tmp = lhs; lhs = rhs; rhs = tmp;ve kopya oluşturma veya atama olan işaretçisiz veri üyeleri bazı veri üyelerini değiştirip bazılarını değiştirememe potansiyeline sahip olabilir. Bu potansiyel std::string, James'in başka bir cevap üzerine yorum yaptığı gibi C ++ 03'ler için bile geçerlidir :

@wilhelmtell: C ++ 03'te, std :: string :: swap (std :: swap olarak adlandırılır) tarafından potansiyel olarak atılan istisnalardan bahsedilmez. C ++ 0x, std :: string :: swap istisna değildir ve istisnalar atmamalıdır. - James McNellis 22 '10 Aralık 15:24


Object Farklı bir nesneden atama yaparken aklı başında görünen atama operatörü uygulaması, kendi kendine atama için kolayca başarısız olabilir. İstemci kodunun kendi kendine atamayı bile denemesi düşünülemez gibi görünse de, kaplardaki algo işlemleri sırasında nispeten kolay olabilir, x = f(x);kodla f(belki sadece bazı #ifdefdallar için) bir makro ala #define f(x) xveya bir referans döndüren bir işlev xveya hatta (muhtemelen verimsiz ama özlü) kod gibi x = c1 ? x * 2 : c2 ? x / 2 : x;). Örneğin:

struct X
{
    T* p_;
    size_t size_;
    X& operator=(const X& rhs)
    {
        delete[] p_;  // OUCH!
        p_ = new T[size_ = rhs.size_];
        std::copy(p_, rhs.p_, rhs.p_ + rhs.size_);
    }
    ...
};

Kendi kendine atamada, yukarıdaki kod silme işlemleri x.p_;, p_yeni tahsis edilen yığın bölgesini işaret eder, daha sonra içindeki başlatılmamış verileri okumaya çalışır (Tanımsız Davranış), eğer bu çok garip bir şey yapmazsa, copyher adalete kendi kendine atama girişiminde bulunur. tahrip 'T'!


⁂ Kopyala ve takas deyimi, fazladan bir geçici kullanımdan (operatörün parametresi kopya oluşturulduğunda) verimsizlik veya sınırlamalar getirebilir:

struct Client
{
    IP_Address ip_address_;
    int socket_;
    X(const X& rhs)
      : ip_address_(rhs.ip_address_), socket_(connect(rhs.ip_address_))
    { }
};

Burada, elle yazılmış bir sunucuya zaten bağlı Client::operator=olup olmadığını kontrol edebilir (belki de faydalıysa "sıfırlama" kodu gönderme), kopyala-takas yaklaşımı ise muhtemelen açılmak üzere yazılacak olan kopya yapıcıyı çağırır farklı bir soket bağlantısıyla orijinali kapatın. Bu, basit bir işlem içi değişken kopyası yerine uzak ağ etkileşimi anlamına gelmekle kalmaz, aynı zamanda soket kaynakları veya bağlantılarında istemci veya sunucu sınırlarının yüksek çalışmasına neden olabilir. (Elbette bu sınıfın oldukça korkunç bir arayüzü var, ama bu başka bir mesele ;-P).*thisrhs


4
Bununla birlikte, bir soket bağlantısı sadece bir örnektir - aynı ilke, donanım problama / başlatma / kalibrasyon, bir iş parçacığı veya rasgele sayılar havuzu oluşturma, belirli şifreleme görevleri, önbellekler, dosya sistemi taramaları, veritabanı gibi pahalı olabilecek herhangi bir başlatma için geçerlidir bağlantıları vb ..
Tony Delroy

Bir tane daha (masif) con var. Mevcut teknik özellikler itibariyle nesnenin bir hareket atama operatörü olmayacaktır ! Daha sonra bir sınıfın üyesi olarak kullanılırsa, yeni sınıfta otomatik olarak oluşturulmuş move-ctor olmaz! Kaynak: youtu.be/mYrbivnruYw?t=43m14s
user362515

3
Kopya atama işleci ile ilgili temel sorun Clientatamanın yasak olmamasıdır.
sbi

İstemci örneğinde, sınıf kopyalanamaz hale getirilmelidir.
John Z. Li

25

Bu cevap daha çok yukarıdaki cevaplara bir ekleme ve hafif bir modifikasyon gibidir.

Visual Studio'nun (ve muhtemelen diğer derleyicilerin) bazı sürümlerinde gerçekten can sıkıcı ve mantıklı olmayan bir hata var. Bu nedenle swapişlevinizi böyle beyan / tanımlarsanız :

friend void swap(A& first, A& second) {

    std::swap(first.size, second.size);
    std::swap(first.arr, second.arr);

}

... swapişlevi çağırdığınızda derleyici size bağırır :

resim açıklamasını buraya girin

Bunun bir friendfonksiyonun çağrılması ve thisnesnenin parametre olarak iletilmesi ile bir ilgisi vardır .


Bunun bir yolu friendanahtar kelime kullanmamak ve swapişlevi yeniden tanımlamaktır :

void swap(A& other) {

    std::swap(size, other.size);
    std::swap(arr, other.arr);

}

Bu kez, sadece arayabilir swapve içeri othergirerek derleyiciyi mutlu edebilirsiniz:

resim açıklamasını buraya girin


Sonuçta, 2 nesneyi değiştirmek için bir işlev kullanmanıza gerek yoktur friend. Parametre olarak swaptek bir othernesneye sahip bir üye işlevinin yapılması mantıklıdır .

thisNesneye zaten erişiminiz var , bu yüzden parametre olarak iletmek teknik olarak gereksizdir.


1
@GManNickG dropbox.com/s/o1mitwcpxmawcot/example.cpp dropbox.com/s/jrjrn5dh1zez5vy/Untitled.jpg . Bu basitleştirilmiş bir versiyon. Parametre friendile bir işlev her çağrıldığında bir hata oluşuyor gibi görünüyor*this
Oleksiy

1
Dediğim gibi @GManNickG, bir hata ve diğer insanlar için iyi çalışabilir. Sadece benimle aynı sorunu yaşayan bazı insanlara yardım etmek istedim. Bunu hem Visual Studio 2012 Express hem de 2013 Preview ile denedim ve bunu yapan tek şey benim modifikasyonum oldu
Oleksiy

8
@GManNickG, tüm resimler ve kod örnekleri ile bir yoruma sığmaz. Ve eğer insanlar aşağı inerse, eminim orada aynı hatayı alan biri var; bu yazıdaki bilgiler tam da ihtiyaç duydukları şey olabilir.
Oleksiy

14
Bu sadece IDE kod vurgulama (IntelliSense) bir hata olduğunu unutmayın ... hiçbir uyarı / hata ile gayet iyi derlemek olacaktır.
Amro

3
Henüz yapmadıysanız (ve düzeltilmemişse) lütfen VS hatasını bildirin. Connect.microsoft.com/VisualStudio
Matt

15

C ++ 11 tarzı ayırıcı kullanan kapsayıcılarla uğraşırken bir uyarı sözcüğü eklemek istiyorum. Takas ve atama çok farklı anlambilime sahiptir.

Somutluktan için bunu bize bir kap düşünelim std::vector<T, A>, Abazı durum bilgisi ayırıcısı türüdür ve aşağıdaki işlevleri karşılaştırmak gerekir:

void fs(std::vector<T, A> & a, std::vector<T, A> & b)
{ 
    a.swap(b);
    b.clear(); // not important what you do with b
}

void fm(std::vector<T, A> & a, std::vector<T, A> & b)
{
    a = std::move(b);
}

Her iki fonksiyonun amacı fsve başlangıçta olan durumu fmvermektir . Ancak, gizli bir soru var: Ne olur ? Cevap, duruma bağlı. Yazalım .aba.get_allocator() != b.get_allocator()AT = std::allocator_traits<A>

  • Eğer AT::propagate_on_container_move_assignmentbir std::true_type, daha sonra fmbir ayırıcı yeniden atar adeğeri ile b.get_allocator()aksi, öyle değil ve aözgün ayırıcı kullanmaya devam eder. Bu durumda, veri öğelerinin depolanması ave buyumlu olmaması nedeniyle veri öğelerinin ayrı ayrı değiştirilmesi gerekir .

  • Eğer AT::propagate_on_container_swapbir std::true_type, daha sonra fsbeklenen şekilde hem veri ve ayırıcılar yeri değiştirilebilir.

  • Öyleyse AT::propagate_on_container_swap, std::false_typedinamik bir kontrole ihtiyacımız var.

    • Eğer a.get_allocator() == b.get_allocator(), daha sonra iki konteyner uyumlu depolama ve her zamanki şekilde takas gelirleri kullanmak.
    • Ancak, a.get_allocator() != b.get_allocator()programın tanımlanmamış bir davranışı varsa (bkz. [Container.requirements.general / 8].

Sonuç olarak, konteyneriniz durumsal ayırıcıları desteklemeye başlar başlamaz, takas C ++ 11'de önemsiz bir işlem haline gelmiştir. Bu biraz "gelişmiş kullanım durumu" olmakla birlikte, tamamen olası değildir, çünkü hareket optimizasyonları genellikle sınıfınız bir kaynağı yönettiğinde ilginç hale gelir ve bellek en popüler kaynaklardan biridir.

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.