Soyut temel sınıflar ve kopya yapımı, temel kurallar


10

Çoğu zaman, nesnenin arayüzünü izole etmek için soyut bir temel sınıfın olması iyi bir fikirdir.

Sorun, kopya yapısının (IMHO) C ++ 'da varsayılan olarak kopuk olması ve kopya kurucularının varsayılan olarak üretilmesidir.

Peki, soyut bir temel sınıf ve türetilmiş sınıflarda ham işaretçiler olduğunda gotchalar nelerdir?

class IAbstract
{
    ~IAbstract() = 0;
}

class Derived : public IAbstract
{
    char *theProblem;
    ...
}

IAbstract *a1 = new Derived();
IAbstract a2 = *a1;//???

Ve şimdi tüm hiyerarşi için kopya yapısını temiz bir şekilde devre dışı bırakıyor musunuz? Kopya yapımını özel olarak ilan ettiniz IAbstractmi?

Soyut temel sınıfları olan üç kural var mı?


1
işaretçiler yerine referanslar kullanın :)
tp1 23

@ tp1: veya en azından akıllı bir işaretçi.
Benjamin Bannier

1
Bazen sadece mevcut kodla çalışmanız gerekir ... Her şeyi anında değiştiremezsiniz.
Kodlayıcı

Neden varsayılan kopya oluşturucunun bozuk olduğunu düşünüyorsunuz?
BЈовић

2
@Coder: Google stil kılavuzu önemsiz bir yığın ve kesinlikle C ++ geliştirmeleri için berbat.
DeadMG

Yanıtlar:


6

Soyut bir sınıfta kopya oluşturma, çoğu durumda atama operatörünün yanı sıra özel yapılmalıdır.

Soyut sınıflar, tanım gereği, polimorfik bir tip olarak yapılmıştır. Böylece örneğinizin ne kadar bellek kullandığını bilmiyorsunuz ve bu nedenle güvenli bir şekilde kopyalayamıyor veya atayamazsınız. Uygulamada dilimleme riskiniz vardır: /programming/274626/what-is-the-slicing-problem-in-c

C ++ 'da polimorfik tip, değere göre manipüle edilmemelidir. Bunları referans veya işaretçi (veya herhangi bir akıllı işaretçi) ile manipüle edersiniz.

Java'nın nesneyi yalnızca başvuru ile işlenebilir hale getirmesinin nedeni ve C # ve D'nin sınıflar ve yapılar arasında ayrılması (ilki polimorfik ve başvuru türü, ikincisi polimorfik olmayan ve değer türü) nedenidir.


2
Java'daki işler daha iyi değil. Java'da, gerçekten ihtiyacınız olduğunda bile bir şeyi kopyalamak acı vericidir ve bunu yapmayı unutması çok kolaydır. Böylece, iki veri yapısının aynı değer sınıfına (örneğin Tarih) başvurduğu kötü hatalarla sonuçlanırsınız ve bunlardan biri Tarih'i değiştirir ve diğer veri yapısı artık bozulur.
kevin cline

3
Meh. Sorun değil - C ++ bilen herkes bu hatayı yapmayacaktır. Daha da önemlisi, ne yaptığınızı biliyorsanız, polimorfik türleri değere göre manipüle etmek için hiçbir neden yoktur. Teknik olarak ince bir olasılık üzerinde tam bir diz sarsıntısı reaksiyonu başlatıyorsunuz. NULL işaretçileri daha büyük bir sorundur.
DeadMG

8

Tabii ki, sadece korumalı ve boş yapabilirsiniz, böylece türetilmiş sınıflar seçebilirsiniz. Bununla birlikte, daha genel olarak, kodunuz zaten yasaklanmıştır, çünkü IAbstractsaflaştırmak imkansızdır - çünkü saf bir sanal işlevi vardır. Bu nedenle, bu genellikle bir sorun değildir - arayüz sınıflarınız örneklenemez ve bu nedenle asla kopyalanamaz ve daha türetilmiş sınıflarınız kopyalamayı istedikleri şekilde yasaklayabilir veya tutabilir.


Ve bir Özet -> Türetilmiş1 -> Türetilmiş2 hiyerarşisi varsa ve Türetilmiş1'e Türetilmiş1 atanırsa? Dilin bu karanlık köşeleri beni hala şaşırtıyor.
Kodlayıcı

@Coder: Bu genellikle PEBKAC olarak görülür. Ancak, daha doğrudan, her zaman özel olduğuna dair karar operator=(const Derived2&)in Derived1.
DeadMG

Tamam, belki bu gerçekten sorun değil. Sadece sınıfın kötüye kullanıma karşı güvenli olduğundan emin olmak istiyorum.
Kodlayıcı

2
Ve bunun için bir madalya almalısın.
Dünya Mühendisi

2
@Coder: Kim tarafından istismar ediliyor? Yapabileceğiniz en iyi şey, doğru kod yazmayı kolaylaştırmaktır. C ++ bilmeyen programcılara karşı savunmaya çalışmak anlamsız.
kevin cline

2

Ctor ve atamayı özel yaparak (veya C ++ 11'de = delete olarak bildirerek) kopyalamayı devre dışı bırakırsınız.

Buradaki nokta, bunu NEREDE yapmanız gerektiğidir. Kodunuzla kalmak için IAbstract bir sorun değildir. (yaptıklarınızı yaparak, *a1 IAbstractalt nesneyi a2'ye atarsınız, herhangi bir referans kaybedersiniz Derived. Değer ataması polimorfik değildir)

Sorun geliyor Derived::theproblem. Türetilmiş bir başkasına kopyalamak aslında paylaşılmak üzere *theproblemtasarlanmamış verileri paylaşabilir ( delete theproblemyıkıcılarında çağırabilecek iki örnek vardır ).

Bu durumda, Derivedkopyalanamaz ve atanamaz olmamalıdır. Tabii ki, özel kopya yaparsanız IAbstract, Derivedihtiyaç için varsayılan kopya olduğu için , kopyalanamaz Derived. Ancak , kopyalama Derived::Derived(const Derived&)çağrısı yapmadan kendiniz tanımlıyorsanız, IAbtractbunları kopyalayabilirsiniz.

Sorun Türetilmiş'tir ve çözüm Türetilmiş olarak kalmalıdır: yalnızca işaretçiler veya başvurularla erişilen yalnızca dinamik bir nesne olması gerekiyorsa,

class Derived
{
    ...
    Derived(const Derived&) = delete;
    Derived& operator=(const Derived&) = delete;
};

Esasen theproblem, ödev ve kopya ile ne yapılacağına karar vermek , Türetilmiş sınıfın tasarımcısına (Türetilmiş'in nasıl çalıştığını ve nasıl yönetildiğini bilmelidir ) bağlıdır.

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.