Yapıcıyı ve = operatör aşırı yüklemesini C ++ 'da kopyala: ortak bir işlev mümkün mü?


87

Bir kopya yapıcıdan beri

MyClass(const MyClass&);

ve bir = operatör aşırı yüklenmesi

MyClass& operator = (const MyClass&);

hemen hemen aynı koda, aynı parametreye sahip ve yalnızca dönüşte farklılık gösteriyorsa, her ikisinin de kullanması için ortak bir işleve sahip olmak mümkün müdür?


6
"... hemen hemen aynı koda sahip ..."? Hmm ... Yanlış bir şey yapıyor olmalısın. Bunun için kullanıcı tanımlı işlevleri kullanma ihtiyacını en aza indirmeye çalışın ve derleyicinin tüm kirli işleri yapmasına izin verin. Bu genellikle kaynakları kendi üye nesnelerinde kapsüllemek anlamına gelir. Bize biraz kod gösterebilirsiniz. Belki bazı iyi tasarım önerilerimiz vardır.
sellibitze

Yanıtlar:


123

Evet. İki genel seçenek vardır. Biri - genellikle önerilmez - operator=from the copy yapıcısını açıkça çağırmaktır :

MyClass(const MyClass& other)
{
    operator=(other);
}

Bununla operator=birlikte, eski durum ve kendi kendini atamadan kaynaklanan sorunlar söz konusu olduğunda, bir mal sağlamak bir zorluktur. Ayrıca, tüm üyeler ve üsler, atanacak olsalar bile önce varsayılan olarak başlatılır other. Bu, tüm üyeler ve üsler için geçerli olmayabilir ve geçerli olduğu yerde bile anlamsal olarak fazlalıktır ve pratik olarak pahalı olabilir.

Giderek daha popüler hale gelen bir çözüm, operator=kopya yapıcı ve takas yöntemini kullanarak uygulamaktır .

MyClass& operator=(const MyClass& other)
{
    MyClass tmp(other);
    swap(tmp);
    return *this;
}

ya da:

MyClass& operator=(MyClass other)
{
    swap(other);
    return *this;
}

Bir swapişlevin yazılması tipik olarak basittir, çünkü sadece dahili elemanların sahipliğini değiştirir ve mevcut durumu temizlemek veya yeni kaynakları tahsis etmek zorunda değildir.

Kopyalama ve takas deyiminin avantajları, otomatik olarak kendi kendine atamanın güvenli olması ve - takas işleminin atılmaması koşuluyla - son derece güvenli olmasıdır.

Son derece istisnai güvenli olmak için, 'elle' yazılmış bir atama operatörü, tipik olarak, atanan kişinin eski kaynaklarını ayırmadan önce yeni kaynakların bir kopyasını tahsis etmek zorundadır, böylece yeni kaynakları tahsis ederken bir istisna meydana gelirse, eski durum yine de geri döndürülebilir. . Tüm bunlar, kopyala ve değiştir ile ücretsiz olarak gelir, ancak genellikle daha karmaşıktır ve bu nedenle sıfırdan yapılması hataya açıktır.

Dikkat edilmesi gereken tek şey, takas yönteminin std::swapkopya yapıcı ve atama operatörünün kendisini kullanan varsayılan değil, gerçek bir takas olduğundan emin olmaktır .

Tipik olarak üye swapolarak kullanılır. std::swapçalışır ve tüm temel türler ve işaretçi türlerinde 'atışsız' garantilidir. Çoğu akıllı işaretçi, atış garantisi ile de değiştirilebilir.


3
Aslında bunlar sıradan işlemler değil. Copy ctor, nesnenin üyelerini ilk kez başlatırken, atama operatörü mevcut değerleri geçersiz kılar. Bunu göz önünde bulundurursak operator=, copy ctor'dan gelen her şey aslında oldukça kötüdür, çünkü önce tüm değerleri, sadece daha sonra diğer nesnenin değerleri ile geçersiz kılmak için bazı varsayılan değerlere başlatır.
sbi

14
Belki "Tavsiye etmiyorum", ekle "ve herhangi bir C ++ uzmanı da yapmıyor". Birisi gelip sadece kişisel bir azınlık tercihini ifade etmediğinizi, bunun hakkında gerçekten düşünenlerin yerleşik fikir birliği fikrini ifade ettiğinizi fark etmeyebilir. Ve, tamam, belki yanılıyorum ve bazı C ++ uzmanları bunu tavsiye ediyor, ama şahsen ben yine de birinin bu öneri için bir referans bulması için elinden geleni yapardım.
Steve Jessop

4
Yeterince adil, zaten size oy verdim :-). Bir şey yaygın olarak en iyi uygulama olarak kabul ediliyorsa, bunu söylemenin en iyisi olduğunu anlıyorum (ve birisi sonuçta gerçekten en iyi olmadığını söylerse tekrar bakın). Aynı şekilde, birisi "C ++ 'da muteks kullanmak mümkün mü" diye sorsa, "oldukça yaygın bir seçenek RAII'yi tamamen yok saymak ve üretimde kilitlenen istisnai olmayan güvenli kod yazmaktır, ancak yazmak giderek daha popüler hale geliyor terbiyeli, çalışma kodu ";-)
Steve Jessop

4
+1. Ve bence her zaman ihtiyaç duyulan analiz var. assignBazı durumlarda (hafif sınıflar için) hem kopya ctor hem de atama operatörü tarafından kullanılan bir üye işlevine sahip olmanın makul olduğunu düşünüyorum . Diğer durumlarda (kaynak yoğun / kullanım durumları, işleme / gövde) elbette bir kopya / takas yöntemi tercih edilir.
Johannes Schaub - litb

2
@litb: Buna şaşırdım, bu yüzden Exception C ++ 'da Madde 41'e baktım (bu duruma dönüştü) ve bu özel öneri gitti ve onun yerine kopyala ve değiştir'i öneriyor. Daha ziyade sinsice "Sorun # 4: Görev Atama için Yetersiz" i aynı anda düşürdü.
CB Bailey

14

Copy yapıcısı, ham bellek olarak kullanılan nesnelerin ilk kez başlatılmasını gerçekleştirir. Atama operatörü OTOH, mevcut değerleri yenileriyle geçersiz kılar. Çoğu zaman bu, eski kaynakları (örneğin, bellek) reddetmeyi ve yenilerini tahsis etmeyi içerir.

İkisi arasında bir benzerlik varsa, o da atama operatörünün imha ve kopya oluşturma gerçekleştirmesidir. Bazı geliştiriciler, atamayı fiilen yerinde imha ve ardından yerleştirme kopyalama-oluşturma yoluyla uygularlardı. Ancak bu çok kötü bir fikir. (Ya bu, türetilmiş bir sınıfın atanması sırasında çağrılan bir temel sınıfın atama operatörüyse?)

Bugünlerde genellikle kanonik deyim olarak kabul edilen şey swap, Charles'ın önerdiği gibi kullanıyor :

MyClass& operator=(MyClass other)
{
    swap(other);
    return *this;
}

Bu, kopya oluşturma ( otherkopyalanan not ) ve imha (işlevin sonunda yok edilir) kullanır - ve bunları da doğru sırada kullanır: yıkımdan önce inşaat (başarısız olabilir) (başarısız olmamalıdır).


Meli swapilan edilmesi virtual?

1
@Johannes: Sanal işlevler, polimorfik sınıf hiyerarşilerinde kullanılır. Değer türleri için atama operatörleri kullanılır. İkisi pek karışmaz.
sbi

-3

Beni rahatsız eden bir şey var:

MyClass& operator=(const MyClass& other)
{
    MyClass tmp(other);
    swap(tmp);
    return *this;
}

Birincisi, zihnim "kopya" derken "takas" kelimesini okumak sağduyumu rahatsız ediyor. Ayrıca, bu süslü numaranın amacını sorguluyorum. Evet, yeni (kopyalanan) kaynakların oluşturulmasında herhangi bir istisna, takas işleminden önce gerçekleşmelidir; bu, tüm yeni verilerin yayınlanmadan önce doldurulduğundan emin olmanın güvenli bir yolu gibi görünüyor.

Bu iyi. Peki, takas sonrasında meydana gelen istisnalar ne olacak? (geçici nesne kapsam dışına çıktığında eski kaynaklar yok edildiğinde) Atama kullanıcısının bakış açısından, işlem başarısız oldu, ancak başarısız oldu. Bunun çok büyük bir yan etkisi var: Kopya gerçekten gerçekleşti. Sadece bazı kaynak temizliği başarısız oldu. İşlem dışarıdan başarısız görünse de hedef nesnenin durumu değiştirildi.

Bu yüzden, "takas" yerine daha doğal bir "transfer" yapmayı öneriyorum:

MyClass& operator=(const MyClass& other)
{
    MyClass tmp(other);
    transfer(tmp);
    return *this;
}

Hala geçici nesnenin inşası var, ancak bir sonraki acil eylem hedefin tüm mevcut kaynaklarını serbest bırakmaktır (ve kaynağın kaynaklarını ona iki kez serbest bırakmamak için BOŞLUKLAR).

{İnşa et, taşı, yok et} yerine, {inşa et, yık, taşı} öneriyorum. En tehlikeli eylem olan hamle, her şey çözüldükten sonra en son yapılan harekettir.

Evet, yıkım başarısızlığı her iki durumda da bir sorundur. Veriler ya bozulur (düşünmediğinizde kopyalanır) veya kaybolur (siz düşünmediğinizde serbest bırakılır). Kaybetmek bozuk olmaktan iyidir. Hiçbir veri kötü veriden daha iyi değildir.

Takas yerine transfer. Zaten benim önerim bu.


2
Bir yıkıcı başarısız olmamalıdır, bu nedenle imha durumunda istisnalar beklenmez. Ve eğer hareket en tehlikeli operasyonsa, hareketi yıkımın arkasına taşımanın avantajı ne olurdu anlamıyorum? Yani, standart şemada, bir taşıma hatası eski durumu bozmaz, oysa yeni planınız bozar. Peki neden? Ayrıca, First, reading the word "swap" when my mind is thinking "copy" irritates-> Bir kütüphane yazarı olarak, genellikle yaygın uygulamaları (kopya + takas) bilirsiniz ve işin püf noktası öyledir my mind. Zihniniz aslında kamusal arayüzün arkasında gizlidir. Yeniden kullanılabilir kodun anlamı budur.
Sebastian Mach
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.