Bir arabirimi temsil eden bir sınıfı nasıl ayarlarım? Bu sadece soyut bir temel sınıf mı?
Bir arabirimi temsil eden bir sınıfı nasıl ayarlarım? Bu sadece soyut bir temel sınıf mı?
Yanıtlar:
Bradtgmurray'ın cevabını genişletmek için, sanal bir yıkıcı ekleyerek arayüzünüzün saf sanal yöntem listesine bir istisna yapmak isteyebilirsiniz. Bu, somut türetilmiş sınıfı ortaya çıkarmadan işaretçi sahipliğini başka bir tarafa geçirmenizi sağlar. Yıkıcı hiçbir şey yapmak zorunda değildir, çünkü arayüzün somut üyeleri yoktur. Bir işlevi hem sanal hem de satır içi olarak tanımlamak çelişkili görünebilir, ancak bana güven - öyle değil.
class IDemo
{
public:
virtual ~IDemo() {}
virtual void OverrideMe() = 0;
};
class Parent
{
public:
virtual ~Parent();
};
class Child : public Parent, public IDemo
{
public:
virtual void OverrideMe()
{
//do stuff
}
};
Sanal yıkıcı için bir gövde eklemek zorunda değilsiniz - bazı derleyicilerin boş bir yıkıcıyı optimize etmekte sorun yaşadıkları ve varsayılanı kullanarak daha iyi durumda olduğunuz ortaya çıkıyor.
=0
bir gövdeli saf bir sanal ( ) yıkıcı tanımlamaktır . Buradaki avantaj, derleyicinin teorik olarak, vtable'ın şu anda geçerli bir üyesi olmadığını görebilmesi ve onu tamamen atabilmesidir. Bir gövdeye sahip bir sanal yıkıcı ile, söz konusu yıkıcı (sanal olarak) örneğin this
işaretçi yoluyla inşaatın ortasında (inşa edilen nesne hala tipteyken) çağrılabilir Parent
ve bu nedenle derleyici geçerli bir vtable sağlamalıdır. Yani this
inşaat sırasında sanal yıkıcıları açıkça çağırmazsanız :) kod boyutundan tasarruf edebilirsiniz.
override
, derleme zamanı bağımsız değişkenine ve dönüş değeri türü denetimine izin verecek anahtar sözcüğü belirtebileceğinizi unutmayın . Örneğin, Çocuk Beyanındavirtual void OverrideMe() override;
Saf sanal yöntemlerle bir sınıf yapın. Bu sanal yöntemleri geçersiz kılan başka bir sınıf oluşturarak arabirimi kullanın.
Saf sanal yöntem, sanal olarak tanımlanan ve 0'a atanan bir sınıf yöntemidir.
class IDemo
{
public:
virtual ~IDemo() {}
virtual void OverrideMe() = 0;
};
class Child : public IDemo
{
public:
virtual void OverrideMe()
{
//do stuff
}
};
override
C ++ 11 hariç
C # / Java'daki soyut temel sınıflara ek olarak özel bir Arabirim türü kategorisine sahip olmanızın tüm nedeni, C # / Java'nın birden fazla mirası desteklememesidir.
C ++ birden fazla kalıtım özelliğini destekler ve bu nedenle özel bir türe gerek yoktur. Soyut olmayan (saf sanal) yöntem içermeyen bir soyut temel sınıf, işlevsel olarak bir C # / Java arabirimine eşdeğerdir.
Thread
örnek olmasını istedim . Çoklu kalıtım, kompozisyonun yanı sıra kötü tasarım olabilir. Her şey duruma bağlıdır.
C ++ 'da kendi başına "arayüz" kavramı yoktur. AFAIK, arayüzler ilk olarak Java'da çoklu kalıtım eksikliği üzerinde çalışmak için tanıtıldı. Bu kavramın oldukça kullanışlı olduğu ortaya çıktı ve aynı etki C ++ 'da soyut bir temel sınıf kullanılarak elde edilebilir.
Soyut temel sınıf, en az bir üye işlevinin (Java lingo'da yöntem) aşağıdaki sözdizimi kullanılarak bildirilen saf bir sanal işlev olduğu bir sınıftır:
class A
{
virtual void foo() = 0;
};
Soyut bir temel sınıf somutlaştırılamaz, yani A sınıfı bir nesneyi bildiremezsiniz. Yalnızca A'dan sınıf türetebilirsiniz, ancak bir uygulama sağlamayan türetilmiş sınıf foo()
da soyut olacaktır. Soyut olmayı durdurmak için, türetilmiş bir sınıf, miras aldığı tüm saf sanal işlevler için uygulamalar sağlamalıdır.
Soyut bir temel sınıfın salt sanal olmayan veri üyeleri ve üye işlevleri içerebileceğinden bir arabirimden daha fazlası olabileceğini unutmayın. Bir arabirimin eşdeğeri, yalnızca saf sanal işlevlere sahip herhangi bir veri içermeyen soyut bir temel sınıf olacaktır.
Mark Ransom'un da belirttiği gibi, soyut bir temel sınıf, tıpkı herhangi bir temel sınıf gibi, sanal bir yıkıcı sağlamalıdır.
Test edebildiğim kadarıyla, sanal yıkıcıyı eklemek çok önemlidir. İle yaratılan new
ve yok edilen nesneleri kullanıyorum delete
.
Sanal yıkıcıyı arayüze eklemezseniz, miras alınan sınıfın yıkıcısı çağrılmaz.
class IBase {
public:
virtual ~IBase() {}; // destructor, use it to call destructor of the inherit classes
virtual void Describe() = 0; // pure virtual method
};
class Tester : public IBase {
public:
Tester(std::string name);
virtual ~Tester();
virtual void Describe();
private:
std::string privatename;
};
Tester::Tester(std::string name) {
std::cout << "Tester constructor" << std::endl;
this->privatename = name;
}
Tester::~Tester() {
std::cout << "Tester destructor" << std::endl;
}
void Tester::Describe() {
std::cout << "I'm Tester [" << this->privatename << "]" << std::endl;
}
void descriptor(IBase * obj) {
obj->Describe();
}
int main(int argc, char** argv) {
std::cout << std::endl << "Tester Testing..." << std::endl;
Tester * obj1 = new Tester("Declared with Tester");
descriptor(obj1);
delete obj1;
std::cout << std::endl << "IBase Testing..." << std::endl;
IBase * obj2 = new Tester("Declared with IBase");
descriptor(obj2);
delete obj2;
// this is a bad usage of the object since it is created with "new" but there are no "delete"
std::cout << std::endl << "Tester not defined..." << std::endl;
descriptor(new Tester("Not defined"));
return 0;
}
Önceki kodu olmadan çalıştırırsanız, virtual ~IBase() {};
yıkıcının Tester::~Tester()
hiçbir zaman çağrılmadığını görürsünüz .
Cevabım temelde diğerleriyle aynı, ancak bence yapacak iki önemli şey daha var:
Arabiriminizde sanal bir yıkıcı bildirin veya biri türdeki bir nesneyi silmeye çalışırsa tanımsız davranışlardan kaçınmak için korumalı sanal olmayan bir tane yapın IDemo
.
Çoklu kalıtımla ilgili sorunları önlemek için sanal kalıtım kullanın. (Arayüzleri kullandığımızda daha çok çoklu kalıtım söz konusudur.)
Ve diğer cevaplar gibi:
Bu sanal yöntemleri geçersiz kılan başka bir sınıf oluşturarak arabirimi kullanın.
class IDemo
{
public:
virtual void OverrideMe() = 0;
virtual ~IDemo() {}
}
Veya
class IDemo
{
public:
virtual void OverrideMe() = 0;
protected:
~IDemo() {}
}
Ve
class Child : virtual public IDemo
{
public:
virtual void OverrideMe()
{
//do stuff
}
}
C ++ 11'de kalıtımdan tamamen kaçınabilirsiniz:
struct Interface {
explicit Interface(SomeType& other)
: foo([=](){ return other.my_foo(); }),
bar([=](){ return other.my_bar(); }), /*...*/ {}
explicit Interface(SomeOtherType& other)
: foo([=](){ return other.some_foo(); }),
bar([=](){ return other.some_bar(); }), /*...*/ {}
// you can add more types here...
// or use a generic constructor:
template<class T>
explicit Interface(T& other)
: foo([=](){ return other.foo(); }),
bar([=](){ return other.bar(); }), /*...*/ {}
const std::function<void(std::string)> foo;
const std::function<void(std::string)> bar;
// ...
};
Bu durumda, bir Arabirimin referans semantiği vardır, yani nesnenin arabirimden daha fazla olduğundan emin olmalısınız (ayrıca değer semantiği ile arabirimler yapmak da mümkündür).
Bu tür arayüzlerin artıları ve eksileri vardır:
Son olarak, kalıtım karmaşık yazılım tasarımındaki tüm kötülüklerin köküdür. Gelen Sean Ebeveyn Değer Semantik ve Polimorfizmi Kavramları tabanlı (tavsiye, bu tekniğin daha iyi sürümleri açıklanmıştır) Aşağıdaki vaka incelenmiştir:
MyShape
Arayüzünü kullanarak polimorfik olarak şekillerimle uğraştığım bir uygulama var diyelim :
struct MyShape { virtual void my_draw() = 0; };
struct Circle : MyShape { void my_draw() { /* ... */ } };
// more shapes: e.g. triangle
Uygulamanızda, YourShape
arayüzü kullanarak farklı şekillerle aynısını yaparsınız :
struct YourShape { virtual void your_draw() = 0; };
struct Square : YourShape { void your_draw() { /* ... */ } };
/// some more shapes here...
Şimdi uygulamanızda geliştirdiğim bazı şekilleri kullanmak istediğinizi varsayalım. Kavramsal olarak, şekillerimiz aynı arayüze sahiptir, ancak şekillerimin uygulamanızda çalışmasını sağlamak için şekillerimi aşağıdaki gibi genişletmeniz gerekir:
struct Circle : MyShape, YourShape {
void my_draw() { /*stays the same*/ };
void your_draw() { my_draw(); }
};
İlk olarak, şekillerimi değiştirmek hiç mümkün olmayabilir. Ayrıca, çoklu kalıtım spagetti koduna giden yolu açar ( TheirShape
arayüzü kullanan üçüncü bir projenin geldiğini hayal edin ... beraberlik işlevlerini de çağırırlarsa ne olur my_draw
?).
Güncelleme: Kalıtım temelli olmayan polimorfizm hakkında birkaç yeni referans var:
Circle
sınıf zayıf bir tasarımdır. Bu Adapter
gibi durumlarda desen kullanmalısınız . Biraz sert geliyorsa özür dilerim, ama Qt
miras hakkında yargıda bulunmadan önce gerçek hayat kütüphanesini kullanmaya çalışın . Kalıtım hayatı daha kolay hale getirir.
Adapter
kalıbı kullanarak düzeltmeye bir örnek (belki de ideone'de) verebilir misiniz ? Avantajlarını görmek istiyorum.
Square
zaten orada olmadığını nasıl bilebilirsin ? Önbilgi? Bu yüzden gerçeklikten koparıldı. Gerçekte, "MyShape" kütüphanesine güvenmeyi seçerseniz, en başından itibaren arayüzüne uyum sağlayabilirsiniz. Şekiller örneğinde çok sayıda saçmalık var (bunlardan biri iki Circle
yapınız var), ancak adaptör böyle bir şeye benziyordu -> ideone.com/UogjWk
Yukarıdaki tüm iyi cevaplar. Aklınızda bulundurmanız gereken ek bir şey var - ayrıca saf bir sanal yıkıcıya da sahip olabilirsiniz. Tek fark, onu hala uygulamanız gerektiğidir.
Şaşkın?
--- header file ----
class foo {
public:
foo() {;}
virtual ~foo() = 0;
virtual bool overrideMe() {return false;}
};
---- source ----
foo::~foo()
{
}
Bunu yapmak istemenizin temel nedeni, benim gibi arayüz yöntemleri sağlamak, ancak bunları geçersiz kılmayı isteğe bağlı yapmaktır.
Sınıfı bir arabirim sınıfı yapmak için saf bir sanal yöntem gerekir, ancak tüm sanal yöntemlerinizin varsayılan uygulamaları vardır, bu nedenle saf sanal yapmak için kalan tek yöntem yıkıcıdır.
Türetilmiş sınıftaki bir yıkıcıyı yeniden uygulamak hiç de önemli değildir - türetilmiş sınıflarımda her zaman sanal veya yok edici bir yıkıcıyı yeniden uygularım.
Microsoft'un C ++ derleyicisini kullanıyorsanız, aşağıdakileri yapabilirsiniz:
struct __declspec(novtable) IFoo
{
virtual void Bar() = 0;
};
class Child : public IFoo
{
public:
virtual void Bar() override { /* Do Something */ }
}
Ben çok daha küçük arayüz kodu sonuçlanır ve oluşturulan kod boyutu önemli ölçüde daha küçük olabilir çünkü bu yaklaşımı seviyorum. Novtable kullanımı, o sınıftaki vtable işaretçisine yapılan tüm referansları kaldırır, böylece asla doğrudan başlatamazsınız. Buradaki belgelere bakın - yeni .
novtable
standart üzerinde kullandığınızı pek anlamıyorumvirtual void Bar() = 0;
= 0;
). Anlamıyorsanız, belgeleri okuyun.
= 0;
ve tam olarak aynı yapmanın standart olmayan bir yolu olduğunu varsaydı.
Orada yazılanlara küçük bir ek:
İlk olarak, yıkıcınızın da saf sanal olduğundan emin olun
İkincisi, sadece iyi önlemler için, uyguladığınızda sanal olarak (normalden ziyade) miras almak isteyebilirsiniz.
NVI (Sanal Olmayan Arayüz Kalıbı) ile uygulanan sözleşme sınıflarını da düşünebilirsiniz. Örneğin:
struct Contract1 : boost::noncopyable
{
virtual ~Contract1();
void f(Parameters p) {
assert(checkFPreconditions(p)&&"Contract1::f, pre-condition failure");
// + class invariants.
do_f(p);
// Check post-conditions + class invariants.
}
private:
virtual void do_f(Parameters p) = 0;
};
...
class Concrete : public Contract1, public Contract2
{
private:
virtual void do_f(Parameters p); // From contract 1.
virtual void do_g(Parameters p); // From contract 2.
};
C ++ geliştirmede hala yeniyim. Visual Studio (VS) ile başladım.
Ancak, hiç kimse __interface
VS (.NET) bahsetmiş gibi görünmüyor . Bunun bir arayüz beyan etmenin iyi bir yolu olup olmadığından emin değilim . Ancak, ek bir yaptırım sağladığı görülmektedir ( belgelerde belirtilmiştir ). Böylece virtual TYPE Method() = 0;
, otomatik olarak dönüştürüleceğinden, açıkça belirtmeniz gerekmez .
__interface IMyInterface {
HRESULT CommitX();
HRESULT get_X(BSTR* pbstrName);
};
Ancak, sadece .NET altında kullanılabilir, çünkü çapraz platform derleme uyumluluğu hakkında endişeliyim çünkü kullanmıyorum.
Bu konuda ilginç bir şey varsa, lütfen paylaşın. :-)
Teşekkürler.
virtual
Bir arabirimi tanımlamak için fiili standart olduğu doğru olsa da , C ++ 'da bir yapıcı ile birlikte gelen klasik C benzeri deseni unutmayalım:
struct IButton
{
void (*click)(); // might be std::function(void()) if you prefer
IButton( void (*click_)() )
: click(click_)
{
}
};
// call as:
// (button.*click)();
Bu, sınıfınızı yeniden yapılandırmanıza gerek kalmadan etkinlik çalışma zamanını yeniden bağlayabilme avantajına sahiptir (C ++ polimorfik türleri değiştirmek için bir sözdizimine sahip olmadığından, bukalemun sınıfları için bir çözümdür).
İpuçları:
click
soyundan gelen yapıcıyı doldurabilirsiniz .protected
üye olarak sahip olabilir ve bir public
referans ve / veya alıcıya sahip olabilirsiniz.if
Kodunuzdaki s ve durum değişikliği sayısına bağlı olarak , bu es veya s'den daha hızlı olabilir (dönüşün 3-4 s civarında olması beklenir , ancak her zaman önce ölçülür.switch()
if
if
std::function<>
fonksiyon işaretçileri üzerinde, sen olabilir içinde tüm nesne verilerini yönetebilir hale IBase
. Bu noktadan sonra, için değer şemalarına sahip olabilirsiniz IBase
(örneğin, std::vector<IBase>
çalışacaktır). Derleyicinize ve STL kodunuza bağlı olarak bunun daha yavaş olabileceğini unutmayın ; ayrıca mevcut uygulamaların, std::function<>
işlev işaretçileri ve hatta sanal işlevlerle karşılaştırıldığında ek yüke sahip olma eğilimindedir (bu gelecekte değişebilir).İşte abstract class
c ++ standardının tanımı
n4687
13.4.2
Soyut sınıf, yalnızca başka bir sınıfın temel sınıfı olarak kullanılabilen bir sınıftır; ondan türetilmiş bir sınıfın alt nesneleri hariç, soyut bir sınıfın hiçbir nesnesi oluşturulamaz. Bir sınıf, en az bir saf sanal işlevi varsa soyuttur.
class Shape
{
public:
// pure virtual function providing interface framework.
virtual int getArea() = 0;
void setWidth(int w)
{
width = w;
}
void setHeight(int h)
{
height = h;
}
protected:
int width;
int height;
};
class Rectangle: public Shape
{
public:
int getArea()
{
return (width * height);
}
};
class Triangle: public Shape
{
public:
int getArea()
{
return (width * height)/2;
}
};
int main(void)
{
Rectangle Rect;
Triangle Tri;
Rect.setWidth(5);
Rect.setHeight(7);
cout << "Rectangle area: " << Rect.getArea() << endl;
Tri.setWidth(5);
Tri.setHeight(7);
cout << "Triangle area: " << Tri.getArea() << endl;
return 0;
}
Sonuç: Dikdörtgen alanı: 35 Üçgen alanı: 17
Soyut bir sınıfın getArea () açısından bir arayüzü nasıl tanımladığını ve diğer iki sınıfın da aynı işlevi uyguladığını, ancak şekle özgü alanı hesaplamak için farklı algoritmayla uyguladıklarını gördük.