Soru Kopya ve takas deyimi nedir?


Bu deyim nedir ve ne zaman kullanılmalıdır? Hangi problemleri çözer? C ++ 11 kullanıldığında deyim değişiyor mu?

Pek çok yerde bahsedilse de, tekil "sorun nedir" sorusu ve cevabı yoktu, işte burada. Daha önce bahsedildiği yerlerin kısmi listesi aşağıdadır:


1671
2017-07-19 08:42


Menşei


gotw.ca/gotw/059.htm Herb Sutter'dan - DumbCoder
Harika, bu soruyu semantiği taşımak için cevap. - fredoverflow
Bu deyim için tam bir açıklamaya sahip olmak iyi bir fikir, herkesin bunu bilmesi çok yaygın. - Matthieu M.
Uyarı: Kopyalama / takas deyimi, faydalı olduğundan çok daha sık kullanılır. Kopyalama atamasından güçlü bir istisna güvenlik garantisi gerekli olmadığında, performans açısından genellikle zararlıdır. Kopyalama ataması için güçlü bir istisna emniyeti gerektiğinde, çok daha hızlı bir kopya atama operatörüne ek olarak kısa bir genel işlev tarafından kolayca sağlanır. Görmek slideshare.net/ripplelabs/howard-hinnant-accu2014 slaytlar 43 - 53. Özet: kopya / takas, araç kutusunda kullanışlı bir araçtır. Ancak fazla pazarlanmış ve daha sonra çoğu kez istismar edilmiştir. - Howard Hinnant
@HowardHinnant: Evet, buna +1. Bunu hemen hemen her C ++ sorununun "bir kopya olduğunda sınıfımın çökmesine yardım et" olduğu ve bu benim yanıtım olduğu bir zamanda yazdım. Kopyalama / taşıma semantiği veya başka bir şeye devam edebilmeniz için çalışmanız yeterlidir, ancak bu gerçekten uygun değildir. Yardım edeceğini düşünüyorsanız, cevabımın tepesine bir sorumluluk reddi beyanı göndermekten çekinmeyin. - GManNickG


Cevaplar:


genel bakış

Neden kopya ve takas deyimine ihtiyacımız var?

Bir kaynağı yöneten herhangi bir sınıf (bir sargıakıllı bir işaretçi gibi uygulamak gerekiyor Büyük Üç. Kopya-kurucu ve yıkıcıların hedefleri ve uygulanması kolay olsa da, kopya atama işletmecisi tartışmalı olarak en nüanslı ve zordur. Nasıl yapılmalı? Ne tuzaklar kaçınılmalıdır?

kopyala ve takas deyim çözümdür ve görev operatörüne iki şeye ulaşmada zarif bir şekilde yardımcı olur: kaçınmak kod çoğaltmave bir güçlü istisna garantisi.

O nasıl çalışır?

kavramsal olarakBu, kopya-kurucunun işlevselliğini kullanarak verilerin yerel bir kopyasını oluşturmak için çalışır, ardından kopyalanan verileri swap eski verileri yeni verilerle değiştirmek. Geçici kopya daha sonra eski verileri onunla birlikte imha eder. Yeni verilerin bir kopyasıyla birlikte kaldık.

Kopyala-ve-takas deyimini kullanmak için üç şeye ihtiyacımız var: çalışan bir kopya-kurucu, bir çalışan yıkıcı (her ikisi de herhangi bir sarıcının temeli, bu yüzden her durumda tamamlanmalıdır) ve swap işlevi.

Bir takas işlevi olmayan atma Bir üyenin iki nesnesini değiştiren işlev, üye için üye. Kullanmaya eğilimli olabiliriz std::swap kendimizi sağlamak yerine, bu imkansız olurdu; std::swap kopya-kurucu ve kopya-atamayı yürüten operatörün uygulanmasında kullanır ve sonuç olarak ödev operatörünü kendi başına tanımlamaya çalışırız!

(Sadece bu değil, şartsız çağrılar swap Özel swap operatörümüzü kullanacak, gereksiz inşaat ve sınıfımızın imha edilmesi üzerine atlayacağız. std::swap gerektirecektir.)


Derinlemesine bir açıklama

Amaç

Somut bir durum düşünelim. Aksi takdirde faydasız 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 ihtiyacı vardır operator= doğru çalışmak.

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

Saf bir uygulamanın nasıl göründüğü aşağıda açıklanmıştır:

// 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 bitirdik diyoruz; Bu artık bir dizi, sızıntı olmadan yönetir. Bununla birlikte, kodda sıralı olarak işaretlenmiş üç sorundan muzdariptir. (n).

  1. Birincisi, öz-atama testidir. Bu kontrol, iki amaca hizmet eder: bu, kendi kendini atamak için gereksiz kodları çalıştırmamızı engellemenin kolay bir yoludur ve bizi ustaca hatalardan korur (örneğin diziyi silmek ve kopyalamak için diziyi silmek gibi). Ancak diğer tüm durumlarda, yalnızca programı yavaşlatmaya ve kodda ses çıkarmaya hizmet eder; Kendini atama nadiren oluşur, bu nedenle çoğu zaman bu kontrol bir israftır. Operatör bu olmadan düzgün çalışabilirse daha iyi olurdu.

  2. İkincisi, sadece temel bir istisna garantisi sağlamasıdır. Eğer new int[mSize] başarısız *this değiştirilmiş olacak. (Yani, boyut yanlış ve veri gitti!) Güçlü bir istisna garantisi için, bir şey olması gerekecek:

    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 bizi üçüncü soruna götürür: kod çoğaltma. Görevlendirme operatörümüz, başka bir yerde yazdığımız tüm kodları etkin bir şekilde çoğaltır ve bu çok korkunç bir şeydir.

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

(Bir şaşkınlık olabilir: eğer bu kadar kod bir kaynağı doğru bir şekilde yönetmek için gerekliyse, eğer sınıfım birden fazla şeyi yönetirse? Bu geçerli bir endişe gibi görünse de, gerçekten de önemsizdir. try/catch cümleleri, bu bir sorun değil. Çünkü bir sınıf yönetmeli sadece bir kaynak!)

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

Belirtildiği gibi, kopyala-ve-takas deyimi tüm bu sorunları giderir. Ama şu anda, bir tane hariç tüm şartlarımız var: swap işlevi. Üçüncüsü, kopya-kurucumuz, ödev operatörümüz ve yıkıcımızın varlığını başarılı bir şekilde ortaya koyarken, “Büyük Üçlü ve Bir Yarısı” olarak adlandırılmalıdır: sınıfınız bir kaynağı yönetirken, swap işlevi.

Sınıfımıza swap 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 açıklama neden public friend swap.) Artık sadece takas edilemez dumb_array's, ancak genel olarak takaslar daha verimli olabilir; tüm dizileri ayırmak ve kopyalamak yerine yalnızca işaretçileri ve boyutları değiştirir. Bu bonus ve işlevsellik dışında, artık kopya ve takas deyimini uygulamaya hazırız.

Daha fazla ado olmadan, görevlendirme operatörümüz:

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

    return *this;
}

Ve bu kadar! Biri düşerken, üç sorunun hepsi bir anda aynı anda ele alınır.

Neden çalışıyor?

Öncelikle önemli bir seçim fark ederiz: parametre argümanı alınır. tarafından değer. Biri aşağıdakileri kolayca yapabilseydi de (ve aslında, deyimin birçok naif uygulaması):

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

    return *this;
}

Kaybediyoruz önemli optimizasyon fırsatı. Sadece bu değil, bu seçim daha sonra tartışılan C ++ 11'de kritiktir. (Genel bir notta, dikkate değer derecede yararlı bir kılavuz şu şekildedir: eğer bir işlevin bir kopyasını yapacaksanız, derleyicinin bunu parametre listesinde yapmasına izin verin.

Her iki durumda da, kaynağımızı elde etmek için kullanılan bu yöntem, kod çoğaltmanın ortadan kaldırılmasının anahtarıdır: kopyayı yapmak için kopya-kurucudan kodu kullanırız ve hiçbir zaman onu tekrarlamaya gerek yoktur. Artık kopya yapıldı, takas etmeye hazırız.

Tüm yeni verinin önceden tahsis edildiği, kopyalandığı ve kullanıma hazır olduğu fonksiyonuna girdikten sonra gözlemleyin. Bu, bize ücretsiz bir istisna garantisi veren bir durumdur: kopyanın yapımı başarısız olursa, bu işleve bile girmeyiz ve bu nedenle de durumu değiştirmek mümkün değildir. *this. (Güçlü bir istisna garantisi için daha önce el ile yaptığımız şey, derleyici bizim için şimdi yapıyor; nasıl bir çeşit.)

Bu noktada evsiziz çünkü swap atma değil. Mevcut verilerimizi kopyalanan verilerle değiştiririz, devletimizi güvenli bir şekilde değiştiririz ve eski veriler geçici hale getirilir. İşlev döndüğünde eski veriler serbest bırakılır. (Parametrenin kapsamı bittiğinde ve onun yıkıcısı çağrıldığında).

Deyim kod tekrar etmediğinden, operatörün içindeki hataları tanıtamıyoruz. Bunun, tek bir düzgün uygulama yapılmasına izin vermek için, kendi başına atama kontrolüne ihtiyaç duyduğumuz anlamına geldiğini unutmayın. operator=. (Ayrıca, kendinden görev atamalarında artık bir performans cezası yoktur.)

Ve bu kopya ve takas deyimidir.

C ++ 11'den ne haber?

C ++, C ++ 11'in bir sonraki sürümü, kaynakları nasıl yönetdiğimiz konusunda çok önemli bir değişiklik yapar: Üçün Kuralı artık Dörtlü Kuralı (ve bir yarım). Niye ya? Çünkü sadece kaynağımızı kopyalayabilmemiz gerekmiyor, onu da inşa etmeliyiz.

Neyse ki bizim için bu çok kolay:

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-inşa etme amacını hatırlayın: kaynakları, sınıfın başka bir örneğinden almak, onu atanabilir ve tahrip edilebilir olmasını garantileyen bir durumda bırakın.

Yani, yaptığımız şey basit: varsayılan kurucu (bir C ++ 11 özelliği) ile başlangıç ​​yapın, ardından other; Sınıfımızın varsayılan yapılandırılmış bir örneğinin güvenli bir şekilde tahsis edilip imha edilebileceğini biliyoruz. other Değiştirildikten sonra aynı şeyi yapabilecek.

(Bazı derleyicilerin kurucu temsilcisini desteklemediğini unutmayın; bu durumda, sınıfı kurmayı manuel olarak varsayılan olarak yapmak zorundayız. Bu, talihsiz ama neyse ki önemsiz bir görevdir.)

Bu neden işe yarıyor?

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

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

Şimdi eğer other bir rengin ile başlatılıyor hareket inşa edilecek. Mükemmel. Aynı şekilde C ++ 03, argüman-by-value değerini alarak, kopya-yapıcı işlevimizi tekrar kullanmamıza izin verir, C ++ 11 otomatik olarak Uygun olduğu zaman hamle yapıcıyı seçin. (Ve tabi ki, daha önce bağlantılı makalede belirtildiği gibi, değerin kopyalanması / taşınması basitçe tamamen seçilebilir.)

Ve böylece kopyalama ve takas deyimini tamamlar.


Dipnotlar

* Neden ayarladık? mArray null Çünkü operatörde başka bir kod atıyorsa, yıkıcı dumb_array çağrılabilir; ve eğer bu, null olarak ayarlanmadan gerçekleşirse, zaten silinmiş olan belleği silmeye çalışırız! Sıfırlama işleminin sıfır olduğundan, bunu boş değere ayarlayarak önleriz.

† Uzmanlaşmamız gereken başka iddialar var. std::swap bizim türüne göre, bir sınıf içi swap yan yana serbest fonksiyon swapvb. Ancak bu gereksizdir: swap vasıfsız bir çağrı ile olacak ve işlevimiz aracılığıyla bulunacaktır ADL. Bir fonksiyon yapacak.

Reason Nedeni basit: kaynağı kendinize verdikten sonra, (C ++ 11) olması gereken yerde takas ve / veya hareket ettirebilirsiniz. Ve kopyayı parametre listesinde yaparak optimizasyonu en üst düzeye çıkarırsınız.


1841
2017-07-19 08:43



@GMan: Bir kerede birkaç kaynağı yöneten bir sınıfın başarısızlığa mahkum olduğunu iddia ediyorum (istisna güvenliği kabus olur) ve bir sınıfın BİR kaynağı yönetmesini VEYA iş işlevselliği ve yöneticileri kullanmasını şiddetle tavsiye ederim. - Matthieu M.
@FrEEzE: "Listenin işlendiği sıraya göre derleyici." Hayır değil. Sınıf tanımında göründükleri sırayla işlenir. Kabul etmeyen bir derleyici std::copy Bu şekilde kırılmış, bozuk derleyiciler için kodlama yapıyorum. Ve son yorumunu anladığımdan emin değilim. - GManNickG
@Freeze: Ayrıca, bu cevabın amacı bir C ++ deyiminden bahsetmektir. Programınızı uyumlu olmayan bir derleyici ile çalışacak şekilde kesmeniz gerekiyorsa, bu iyi bir şeydir, ama benim sorumluluğum gibi davranmaya çalışmayın ya da "kötü uygulama" yapmamaya çalışmayın lütfen. - GManNickG
Takas yönteminin neden arkadaş olarak bildirildiğini anlamıyorum. - szx
@neuviemeporte: Senin ihtiyacın var swap ADL sırasında bulunduğunuz gibi rastlayacağınız çoğu genel kodda çalışmak istiyorsanız, boost::swap ve diğer çeşitli takas örnekleri. Swap, C ++ 'da zor bir sorundur ve genellikle hepimiz, tek bir erişim noktasının (tutarlılık için) en iyi olduğunu ve genel olarak bunu yapmanın tek yolunun özgür bir işlev olduğunu kabul ettiğimiz oldu.int örneğin bir takas üyesine sahip olamaz). Görmek benim sorum bazı arka plan için. - GManNickG


Atama, kalbinde, iki adımdır: nesnenin eski halini yırtmak ve yeni devletini bir kopya olarak inşa etmek diğer bazı nesnelerin durumu.

Temel olarak, çöp yakma fırını ve kopya kurucu Yani, ilk fikir, işi onlara devretmek olurdu. Ancak yıkım başarısız olmamalı, inşaat sırasında biz aslında bunu başka bir şekilde yapmak istiyoruz: önce yapıcı kısmı gerçekleştir ve eğer başarılı olursa sonra yıkıcı kısmı yapmak. Kopyala-ve-takas deyimi bunu yapmanın bir yoludur: İlk önce bir geçici kopya oluşturmak için bir 'kopya kurucusu' çağırır, daha sonra geçici olarak verileriyle değiştirir ve sonra geçici yıkıcı eski durumu yok eder.
Dan beri swap() asla başarısız olmaz, başarısız olan tek parça kopya yapımıdır. Bu önce yapılır ve eğer başarısız olursa hedeflenen nesnede hiçbir şey değişmez.

Rafine edilmiş haliyle, kopyala ve takas işlemi, kopyanın görevlendiricinin (referans olmayan) parametresinin başlatılmasıyla gerçekleştirilmesiyle gerçekleştirilir:

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

227
2017-07-19 08:55



Pimpl'den bahsetmenin kopya, takas ve yıkımdan bahsetmek kadar önemli olduğunu düşünüyorum. Takas sihirli bir istisna değildir. Özel durum güvenlidir, çünkü takas işaretçileri istisna güvenlidir. Yapmıyorsun var Bir pimpl kullanmak, ancak eğer o zaman yapmazsanız, bir üyenin her takasının istisna güvenli olduğundan emin olmalısınız. Bu üyeler değişebilince bir kabus olabilir ve bir pimpl'in arkasına saklandıklarında önemsizdir. Ve sonra, sonra pimpl maliyetini geliyor. Bu da bizi, istisnai-emniyetin performansta bir maliyete sahip olduğu sonucuna götürüyor. - wilhelmtell
std::swap(this_string, that) no-throw garantisi vermez. Güçlü bir istisna emniyeti sağlar, ancak no-throw garantisi yoktur. - wilhelmtell
@wilhelmtell: C ++ 03'te, potansiyel olarak atılan istisnalardan bahsedilmiyor. std::string::swap (tarafından denir std::swap). C ++ 0x'da, std::string::swap olduğu noexcept ve istisnalar atmamalı. - James McNellis
@sbi @JamesMcNellis tamam, ancak nokta hala geçerli: Eğer sınıf-tipi üyeleriniz varsa, bunları değiştirmenin no-throw olduğundan emin olmalısınız. İşaretçi olan tek bir üyeniz varsa, bu önemsizdir. Aksi halde değil. - wilhelmtell
@wilhelmtell: Takas etme noktasının bu olduğunu düşündüm: Asla atar ve her zaman O (1) (evet, biliyorum, std::array...) - sbi


Zaten iyi cevaplar var. Ben odaklanacağım ağırlıklı olarak Onlar eksik düşünüyorum - kopya ve takas deyim ile "eksileri" bir açıklama ....

Kopya ve takas deyimi nedir?

Bir takas işlevine göre atama operatörünü uygulama yolu:

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

Temel fikir şudur:

  • Bir nesneye atanmanın en çok hataya eğilimli olan kısmı, yeni durum ihtiyaçlarının elde edildiği her türlü kaynağın sağlanmasıdır (ör., hafıza, tanımlayıcılar).

  • bu satın alma denenebilir önce nesnenin mevcut durumunu değiştirmek (ör. *this) Yeni değerin bir kopyası yapılmışsa, bu yüzden rhs kabul edildi değere göre (yerine kopyalandı) referans olarak

  • yerel kopyanın durumunu değiştirmek rhs ve *this olduğu genellikle potansiyel kopyalama / istisnalar olmadan nispeten kolay, yerel kopya daha sonra herhangi bir özel duruma ihtiyaç duymadığı sürece (sadece bir nesneyi olduğu gibi çalıştırmak için yıkıcıya uygun duruma ihtiyaç duyar) taşındı den> = C ++ 11)

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

  • Atanan bir nesnenin, bir istisna atan bir ödevden etkilenmemesini istediğinizde, swap güçlü istisna garantisi ile ve ideal olarak başarısız olan biri /throw.. †

  • Atama işlecini (basit) kopyalama kurucusu açısından tanımlamak için temiz, anlaşılması kolay, sağlam bir yol istediğinizde, swap ve yıkıcı fonksiyonları.

    • Kopyalama-takas olarak yapılan kendi kendini atamak, göz ardı edilemeyen kenar durumlarını önler.

  • Ödev sırasında fazladan bir geçici nesneye sahip olmanın yarattığı herhangi bir performans cezası veya anlık olarak daha yüksek kaynak kullanımı, uygulamanız için önemli değildir. ⁂

swap fırlatma: genellikle veri elemanlarının nesnelerin işaretçi tarafından izlendiğini, ancak atılmayan takas içermeyen işaretçi olmayan veri elemanlarının veya takas işleminin, X tmp = lhs; lhs = rhs; rhs = tmp; ve kopya-inşaat veya atama atabilir, hala bazı veri üyeleri değiş tokuş ve diğerleri bırakarak başarısızlık potansiyeline sahip olabilir. Bu potansiyel C ++ 03 için bile geçerlidir std::stringJames'in başka bir cevapta yorumladığı gibi:

@wilhelmtell: C ++ 03'te, std :: string :: swap (std :: swap tarafından çağrılan) tarafından potansiyel olarak atılan istisnalardan bahsedilmiyor. C ++ 0x'de, std :: string :: swap noexcept ve istisnalar atmamalı. - 15: 24'te James McNellis Dec 22 '10


Dist Ayrı bir nesneden atama yaparken aklı başında olan atama operatörü uygulaması, kendi kendini atamak için kolayca başarısız olabilir. İstemci kodunun kendiliğinden atamayı bile denemeyeceği düşünülemez gibi görünse de, kaplardaki algo işlemleri sırasında göreceli olarak kolayca gerçekleşebilir. x = f(x); kod nerede f (belki sadece bazıları için #ifdef dalları) bir makro ala #define f(x) x veya bir referansa dönen bir işlev xveya hatta (muhtemelen etkisiz ama özlü) gibi kod 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_);
    }
    ...
};

Kendiliğinden atanmada, yukarıdaki kod silme x.p_;, puanlar p_ yeni ayrılmış bir yığın bölgesinde, daha sonra okumaya çalışır. başlatılmamış Buradaki veriler (Tanımsız Davranış), eğer bu çok tuhaf bir şey yapmazsa, copy Her yıkılan 'T' ye bir görev atamaya çalışır!


Copy Kopya ve takas deyimi, fazladan bir geçici kullanımdan dolayı (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_))
    { }
};

İşte, el yazısıyla yazılmış Client::operator= kontrol edebilir mi *this zaten aynı sunucuya bağlı rhs (eğer kullanışlısa, bir "sıfırlama" kodu gönderilebilir), kopyalama ve takas yaklaşımı, muhtemelen farklı bir soket bağlantısı açmak için yazılan kopya-kurucuyu çağırır ve sonra orijinali kapatır. Basit bir süreç içi değişken kopyası yerine uzak ağ etkileşimi anlamına gelmekle kalmaz, aynı zamanda soket kaynaklarında veya bağlantılarında istemci veya sunucu limitleri oluşturabilir. (Tabii ki bu sınıf oldukça korkunç bir arayüze sahip, ama bu başka bir konu; -P).


32
2018-03-06 14:51



Bu, bir soket bağlantısının sadece bir örnektir - aynı prensip, donanım problama / başlatma / kalibrasyon, bir iş parçacığı havuzu veya rasgele sayılar oluşturma, belirli kriptografi görevleri, önbellekler, dosya sistemi taramaları, veritabanı gibi herhangi bir potansiyel olarak pahalı başlatma için geçerlidir. bağlantılar vb .. - Tony Delroy
Bir daha (büyük) con var. Mevcut özelliklerden teknik olarak nesne olacak hareket atama operatörü yok! Daha sonra bir sınıfın üyesi olarak kullanılırsa, yeni sınıf otomatik olarak üretilen move-ctor olmayacak! Kaynak: youtu.be/mYrbivnruYw?t=43m14s - user362515
Kopya atama operatörü ile ana sorun Client atama yasak değildir. - sbi


Bu cevap, yukarıdaki cevaplarda bir ekleme ve hafif bir değişiklik gibidir.

Visual Studio'nun bazı sürümlerinde (ve muhtemelen diğer derleyiciler) gerçekten rahatsız edici ve mantıklı olmayan bir hata vardır. Yani eğer beyan ederseniz / tanımlarsanız swap bunun gibi işlev:

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

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

}

... derlediğinde derleyici sana bağırır swap fonksiyon:

enter image description here

Bunun bir şeyle ilgisi var. friend çağrılan işlev this nesne parametre olarak iletiliyor.


Bunun bir yolu kullanmamak friend anahtar kelime ve yeniden tanımlayın swap fonksiyon:

void swap(A& other) {

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

}

Bu sefer sadece arayabilirsin swap ve geçmek otherBöylece derleyiciyi mutlu eder:

enter image description here


Sonuçta sen gerek kullanmak friend 2 nesneyi takas işlevi. Bu kadar mantıklı swap bir üyesi olan bir üye işlevi other Bir parametre olarak nesne.

Zaten erişiminiz var this Nesne, bu yüzden parametre olarak iletmek teknik olarak gereksizdir.


19
2017-09-04 04:50



Hatayı yeniden üreten örneğinizi paylaşabilir misiniz? - GManNickG
@GManNickG dropbox.com/s/o1mitwcpxmawcot/example.cpp  dropbox.com/s/jrjrn5dh1zez5vy/Untitled.jpg. Bu basitleştirilmiş bir versiyon. Her seferinde bir hata oluşuyor friend işlevi ile çağrılır *this parametre - Oleksiy
@GManNickG, tüm görseller ve kod örnekleri ile bir yoruma uymaz. Ve eğer insanlar reddederse sorun değil, eminim ki, aynı hatayı alan birileri var. Bu yazıdaki bilgiler sadece ihtiyaç duydukları şey olabilir. - Oleksiy
Bunun sadece IDE kodunda (IntelliSense) vurgulanan bir hata olduğuna dikkat edin ... Hiçbir uyarı / hata olmadan sadece iyi bir şekilde derlenecektir. - Amro
Daha önce yapmadıysanız (ve düzeltilmemişse) lütfen VS hatasını buradan bildirin. connect.microsoft.com/VisualStudio - Matt


C ++ 11 stili ayırıcı tanımlı kaplarla uğraşırken uyarı eklemek istiyorum. Takas ve atama, subtly farklı semantik var.

Somut olarak, bir konteyner düşünelim std::vector<T, A>, nerede A bazı durumlu ayırıcı türüdür ve aşağıdaki işlevleri karşılaştırırız:

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ı fs ve fm vermek a devlet b başlangıçta vardı. Ancak, gizli bir soru var: ne olur? a.get_allocator() != b.get_allocator()? Cevap, duruma bağlı. Hadi yaz AT = std::allocator_traits<A>.

  • Eğer AT::propagate_on_container_move_assignment olduğu std::true_type, sonra fm ayrılmayı yeniden atar a değeri ile b.get_allocator()aksi halde olmaz ve a orijinal ayırıcısını kullanmaya devam ediyor. Bu durumda, veri elemanlarının saklanması gerektiğinden, veri öğelerinin ayrı ayrı değiştirilmesi gerekir. a ve b uyumlu değil.

  • Eğer AT::propagate_on_container_swap olduğu std::true_type, sonra fs Hem verileri hem de ayırıcıları beklenen şekilde değiştirir.

  • Eğer AT::propagate_on_container_swap olduğu std::false_typeo zaman dinamik bir kontrole ihtiyacımız var.

    • Eğer a.get_allocator() == b.get_allocator()Daha sonra iki konteyner uyumlu depolama kullanır ve takas işlemi her zamanki gibi devam eder.
    • Ancak eğer a.get_allocator() != b.get_allocator(), program var tanımlanmamış davranış (cf. [container.requirements.general / 8].

Sonucunuz, kapsayıcınız, durumunuzu belirleyici ayırıcıları desteklemeye başlar başlamaz C ++ 11'de önemsiz olmayan bir işlem haline geldi. Bu biraz "gelişmiş kullanım durumu" dır, ancak bu tamamen olası değildir çünkü hareket optimizasyonları genellikle sınıfınız bir kaynağı yönetdiğinde ilginç olur ve bellek en popüler kaynaklardan biridir.


10
2018-06-24 08:16