Not: Aşağıdakiler C ++ 03 kodudur, ancak önümüzdeki iki yıl içinde C ++ 11'e geçmeyi bekliyoruz, bu yüzden bunu aklımızda tutmalıyız.
C ++ 'ta soyut bir arayüzün nasıl yazılacağı hakkında bir kılavuz (yeni başlayanlar için, diğerleri arasında) yazıyorum. Konuyla ilgili her iki Sutter makalesini de okudum, örnekler ve cevaplar için interneti aradım ve bazı testler yaptım.
Bu kod derlenmemelidir!
void foo(SomeInterface & a, SomeInterface & b)
{
SomeInterface c ; // must not be default-constructible
SomeInterface d(a); // must not be copy-constructible
a = b ; // must not be assignable
}
Yukarıdaki tüm davranışlar, dilimleme konusundaki problemlerinin kaynağını bulur : Soyut arabirim (veya hiyerarşideki yaprak olmayan sınıf), türetilebilir sınıf olabilirse, inşa edilemez veya kopyalanamaz / atanabilir, EVEN olmamalıdır.
0'ınci Çözüm: temel arayüz
class VirtuallyDestructible
{
public :
virtual ~VirtuallyDestructible() {}
} ;
Bu çözüm sade ve biraz naif: Tüm kısıtlamalarımız başarısız oluyor: Varsayılan olarak oluşturulmuş, kopya oluşturulmuş ve kopya atanmış olabilir (Taşıyıcıları ve atamayı taşımaktan bile emin değilim, ancak hala 2 yıla sahibim. dışarı).
- Yıkıcıyı saf sanal olarak ilan edemeyiz çünkü satır içi olarak tutmamız gerekir ve derleyicilerimizden bazıları saf sanal yöntemleri satır içi boş gövdeyle sindirmez.
- Evet, bu sınıfın tek amacı uygulayıcıları neredeyse yok edilebilecek bir duruma getirmek, ki bu nadir bir durum.
- Ek bir sanal saf yöntemimiz olsa bile (vakaların çoğunluğu), bu sınıf yine de kopyalanabilir.
Yani hayır ...
1. Çözüm: boost :: tartışılmaz
class VirtuallyDestructible : boost::noncopyable
{
public :
virtual ~VirtuallyDestructible() {}
} ;
Bu çözüm en iyisidir, çünkü açık, net ve C ++ (makro yok)
Sorun, bu özel arabirim için hala çalışmamasıdır çünkü VirtuallyConstructible hala varsayılan olarak yapılandırılabilir .
- Yıkıcıyı tamamen sanal ilan edemeyiz çünkü satır içi olarak tutmamız gerekir ve derleyicilerimizden bazıları sindirmez.
- Evet, bu sınıfın tek amacı uygulayıcıları neredeyse yok edilebilecek bir duruma getirmek, ki bu nadir bir durum.
Bir başka sorun da , kopyalanamayan arayüzü uygulayan sınıfların, bu yöntemlere sahip olmaları gerekiyorsa (ve bizim kodumuzda hala müşterimiz tarafından erişilebilen değer sınıflarına sahip olmamız durumunda) açıkça kopya kurucuyu ve atama işlecini açıklamak / tanımlamak zorunda olmasıdır. arayüzler).
Bu, gitmek istediğimiz yer olan Sıfır Kuralına aykırıdır: Eğer varsayılan uygulama uygunsa, onu kullanabilmeliyiz.
2. Çözüm: korunmalarını sağlayın!
class MyInterface
{
public :
virtual ~MyInterface() {}
protected :
// With C++11, these methods would be "= default"
MyInterface() {}
MyInterface(const MyInterface & ) {}
MyInterface & operator = (const MyInterface & ) { return *this ; }
} ;
Bu kalıp sahip olduğumuz teknik kısıtlamaları takip eder (en azından kullanıcı kodunda): MyInterface varsayılan olarak oluşturulamaz, kopya ile oluşturulamaz ve kopya ile atanamaz.
Ayrıca, uygulama sınıfları için Sıfır Kuralını takip etmekte özgür olan, hatta birkaç kurucu / işletmeciyi C ++ 11 / 14'te sorunsuz bir şekilde "= default" olarak ilan edebileceği hiçbir yapay kısıtlama getirmez .
Şimdi, bu oldukça ayrıntılı ve bir alternatif bir makro kullanıyor olabilir:
class MyInterface
{
public :
virtual ~MyInterface() {}
protected :
DECLARE_AS_NON_SLICEABLE(MyInterface) ;
} ;
Korumalı makro dışında kalmalıdır (çünkü kapsamı yoktur)
Doğru şekilde "ad alanı" (yani, şirketinizin veya ürününüzün adıyla önceden belirlenmiş), makronun zararsız olması gerekir.
Bunun avantajı, kodun tüm arayüzlere kopyalanmak yerine, tek bir kaynaktan etkilenmesidir. Hareket yapıcı ve hareket ataması gelecekte de aynı şekilde açıkça devre dışı bırakılırsa, bu kodda çok hafif bir değişiklik olur.
Sonuç
- Arayüzlerde kodun dilimlemeye karşı korunmasını istemek paranoyak mıyım? (Yapmadığıma inanıyorum, ama kimse bilmiyor ...)
- Yukarıdakiler arasında en iyi çözüm nedir?
- Başka, daha iyi bir çözüm var mı?
Lütfen bunun yeni başlayanlar için bir rehber niteliğinde olacak bir model olduğunu unutmayın (diğerleri arasında), bu nedenle şöyle bir çözüm var: "Her vaka kendi uygulamasına sahip olmalı" uygulanabilir bir çözüm değildir.
Ödül ve sonuçlar
Ben lütuf layık coredump çünkü sorulara cevap harcanan ve cevapları alaka.
Soruna olan çözümüm muhtemelen şöyle bir şeye gider:
class MyInterface
{
DECLARE_CLASS_AS_INTERFACE(MyInterface) ;
public :
// the virtual methods
} ;
... aşağıdaki makroyla:
#define DECLARE_CLASS_AS_INTERFACE(ClassName) \
public : \
virtual ~ClassName() {} \
protected : \
ClassName() {} \
ClassName(const ClassName & ) {} \
ClassName & operator = (const ClassName & ) { return *this ; } \
private :
Bu, aşağıdaki nedenlerle sorunum için uygun bir çözüm:
- Bu sınıf somutlaştırılamaz (inşaatçılar korunur)
- Bu sınıf neredeyse imha edilebilir
- Bu sınıf, miras sınıflarına aşırı sınırlamalar getirmeden miras alınabilir (örneğin miras sınıfı varsayılan olarak kopyalanabilir)
- Makro kullanımı, "bildirim" arayüzünün kolayca tanınabilir (ve aranabilir) anlamına gelir ve kodu tek bir yerde değiştirilir, değiştirilmesini kolaylaştırır (uygun bir önek adı istenmeyen ad çakışmalarını kaldıracaktır)
Diğer cevapların değerli bilgiler verdiğini unutmayın. Bunu yapan herkese teşekkür ederim.
Sanırım bu soruya yine başka bir ödül getirebilirim ve bir tane görürsem, sadece bu cevaba vermek için bir ödül açacağım kadar aydınlatıcı cevaplara değer veriyorum.
virtual ~VirtuallyDestructible() = 0
ve arabirim sınıflarının sanal mirası (sadece soyut üyelerle). Bunu, VirtuallyDestructible, belki de ihmal edebilirsin.
virtual void bar() = 0;
Örneğin? Bu, ara yüzünüzün tanıtılmasını engeller.