C ++ 'da bir arayüzü nasıl ilan edersiniz?


Yanıtlar:


686

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.


106
Sanal desctuctor ++! Bu çok önemli. Ayrıca derleyicinin sizin için otomatik olarak oluşturmasını önlemek için operatörün = saf sanal bildirimlerini ve kopya yapıcı tanımlarını dahil etmek isteyebilirsiniz.
xan

33
Sanal yıkıcıya bir alternatif, korumalı bir yıkıcıdır. Bu, bazı durumlarda daha uygun olabilecek polimorfik yıkımı devre dışı bırakır. Gotw.ca/publications/mill18.htm adresinde "Yönerge # 4" ifadesini arayın .
Fred Larson

9
Diğer bir seçenek, =0bir 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 thisişaretçi yoluyla inşaatın ortasında (inşa edilen nesne hala tipteyken) çağrılabilir Parentve bu nedenle derleyici geçerli bir vtable sağlamalıdır. Yani thisinşaat sırasında sanal yıkıcıları açıkça çağırmazsanız :) kod boyutundan tasarruf edebilirsiniz.
Pavel Minaev

51
Ne kadar tipik bir C ++ cevap en iyi cevap doğrudan soruya cevap değil (açıkçası kod mükemmel olsa da), bunun yerine basit cevabı optimize eder.
Tim

18
C ++ 11'de 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;
Sean

245

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
        }
};

29
IDemo'da yapacak bir şey yok ediciniz olmalı, böylece yapılacak davranış tanımlanır: IDemo * p = new Child; / * her neyse * / sil p;
Evan Teran

11
Child sınıfındaki OverrideMe yöntemi neden sanaldır? Bu gerekli mi?
Cemre

9
@Cemre - hayır gerekli değil, ama o da acıtmıyor.
PowerApp101

11
Sanal bir yöntemi geçersiz kılarken anahtar kelimeyi 'sanal' tutmak genellikle iyi bir fikirdir. Gerekli olmasa da, kodu daha net hale getirebilir - aksi takdirde, bu yöntemin polimorfik olarak kullanılabileceğine veya hatta temel sınıfta mevcut olduğuna dair hiçbir işaretiniz yoktur.
Kevin

27
@Kevin overrideC ++ 11 hariç
keyser

146

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.


17
Arayüzler oluşturabilmemiz, bizi çok fazla yazmaktan (sanal, = 0, sanal yıkıcı) kurtarmak yine de iyi olurdu. Ayrıca çoklu kalıtım benim için gerçekten kötü bir fikir gibi görünüyor ve pratikte kullanıldığını hiç görmedim, ancak arayüzlere her zaman ihtiyaç duyuluyor. Kötü için C ++ comity sadece onları istiyorum çünkü arayüzleri tanıtmak olmaz.
Ha11owed

9
Ha11owed: Arayüzleri var. Bunlara saf sanal yöntemleri olan ve yöntem uygulaması olmayan sınıflar denir.
Miles Rout

6
@doc: java.lang.Thread'de muhtemelen nesnenizde olmasını istemediğiniz yöntemler ve sabitler vardır. Thread ve genel yöntem checkAccess () ile başka bir sınıftan genişletirseniz derleyici ne yapmalıdır? Gerçekten C ++ gibi güçlü adlandırılmış temel işaretçiler kullanmayı tercih eder misiniz? Bu kötü bir tasarıma benziyor, genellikle birden fazla mirasa ihtiyacınız olduğunu düşündüğünüz kompozisyona ihtiyacınız var.
Ha11owed

4
@ Ha11ow uzun zaman önceydi, bu yüzden ayrıntıları hatırlamıyorum, ancak sınıfımda olmasını istediğim yöntemler ve sabitler vardı ve daha da önemlisi, türetilmiş sınıf nesnemin bir 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.
doc

2
@Dave: Gerçekten mi? Objective-C derleme zamanı değerlendirmesi ve şablonları var mı?
Tekilleştirici

51

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.


13
Birden fazla mirasın yerine "çoklu kalıtım eksikliği" den daha fazlasını söyleyebilirim. Java en başından beri böyle tasarlandı çünkü çoklu miras çözdüğünden daha fazla sorun yaratır. İyi cevap
OscarRyz

11
Oscar, Java öğrenmiş bir C ++ programcısı olup olmadığınıza bağlıdır. :) IMHO, akıllıca kullanılırsa, C ++ 'da neredeyse her şey gibi, çoklu kalıtım sorunları çözer. Bir "arayüz" soyut temel sınıf, çok kalıtımın çok mantıklı kullanımına bir örnektir.
Dima

8
@OscarRyz Yanlış. MI sadece yanlış kullanıldığında sorun yaratır. MI ile ilgili iddia edilen sorunların çoğu alternatif tasarımlarla da ortaya çıkacaktır (MI olmadan). İnsanlar MI ile tasarımlarında bir sorun yaşadıklarında, bu MI'nın hatasıdır; SI ile bir tasarım problemi varsa, bu onların kendi hatasıdır. "Ölüm pırlantası" (tekrarlanan miras) buna en iyi örnektir. MI dayak saf ikiyüzlülük değil, yakındır.
curiousguy

4
Anlamsal olarak, arayüzler soyut sınıflardan farklıdır, bu nedenle Java'nın arayüzleri sadece teknik bir çözüm değildir. Bir arayüz veya soyut bir sınıf tanımlama arasındaki seçim, teknik değerlendirmelerden değil semantikten kaynaklanır. Bazı arayüz "HasEngine" düşünelim: bu bir özellik, bir özellik ve çok farklı türlere (sınıflar veya soyut sınıflar) uygulanabilir / uygulanabilir, bu yüzden soyut bir sınıf değil, bunun için bir arayüz tanımlayacağız.
Marek Stanley

2
@MarekStanley, haklı olabilirsin, ama keşke daha iyi bir örnek seçseydin. Bir arayüzü miras almak mı yoksa bir uygulamayı miras almak açısından düşünmeyi seviyorum. C ++ 'da hem arabirimi hem de uygulamayı birlikte miras alabilirsiniz (genel miras) veya yalnızca uygulamayı miras alabilirsiniz (özel miras). Java'da uygulama olmadan yalnızca arabirimi devralma seçeneğiniz vardır.
Dima

43

Test edebildiğim kadarıyla, sanal yıkıcıyı eklemek çok önemlidir. İle yaratılan newve 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 .


3
Pratik, derlenebilir bir örnek sunarak bu konudaki en iyi cevap budur. Şerefe!
Lumi

1
Testet :: ~ Tester () yalnızca obj "Tester ile Bildirildi" durumunda çalışır.
Alessandro L.

Aslında, dize özel adının yıkıcısı çağrılır ve bellekte ayrılacak olan budur. Çalışma zamanı söz konusu olduğunda, bir sınıfın tüm somut üyeleri yok edildiğinde, sınıf örneği de öyle. İki Nokta yapısı olan bir Line sınıfı ile benzer bir deneyi denedim ve her iki yapının silme çağrısında ya da çevreleyen fonksiyondan dönüşte her iki yapının da yıkıldığını gördüm. valgrind 0 sızıntısını doğruladı.
Chris Reid

33

Cevabım temelde diğerleriyle aynı, ancak bence yapacak iki önemli şey daha var:

  1. 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.

  2. Ç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:

  • 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.

    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
            }
    }

2
arayüzde veri üyeleriniz olmadığından sanal kalıtım gerekmez.
Robocide

3
Sanal kalıtım yöntemler için de önemlidir. O olmadan, OverrideMe () ile belirsizlikler yaşarsınız, bunun 'örneklerinden' biri saf sanal olsa bile (bunu kendim denedim).
Knarf Navillus

5
@Avishay_ "Arayüzde veri üyeniz olmadığı için sanal kalıtım gerekmez. " Yanlış.
curiousguy

WinAVR 2010 ile birlikte gelen 4.3.3 sürümünde sanal kalıtımın bazı gcc sürümlerinde çalışmayabileceğine dikkat edin: gcc.gnu.org/bugzilla/show_bug.cgi?id=35067
mMontu

Sanal olmayan korumalı bir
Wolf

10

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:

MyShapeArayü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, YourShapearayü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 ( TheirShapearayü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:


5
TBH kalıtım, bir arayüz gibi görünen, ancak bazı tutarsız tasarımları bağlamak için bir tutkal olan C ++ 11 şeyinden çok daha açıktır. Şekiller örneği gerçeklikten koparılır ve Circlesınıf zayıf bir tasarımdır. Bu Adaptergibi durumlarda desen kullanmalısınız . Biraz sert geliyorsa özür dilerim, ama Qtmiras hakkında yargıda bulunmadan önce gerçek hayat kütüphanesini kullanmaya çalışın . Kalıtım hayatı daha kolay hale getirir.
doc

2
Hiç sert gelmiyor. Şekil örneği gerçeklikten nasıl ayrılıyor? Circle'ı Adapterkalıbı kullanarak düzeltmeye bir örnek (belki de ideone'de) verebilir misiniz ? Avantajlarını görmek istiyorum.
gnzlbg

Tamam bu minik kutuya sığmaya çalışacağım. Her şeyden önce, çalışmanızı güvence altına almak için kendi uygulamanızı yazmaya başlamadan önce genellikle "MyShape" gibi kütüphaneleri seçersiniz. Yoksa Squarezaten 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 Circleyapınız var), ancak adaptör böyle bir şeye benziyordu -> ideone.com/UogjWk
doc

2
O zaman gerçeklikten kopuk değil. A şirketi B şirketini satın aldığında ve B şirketinin kod tabanını A'ya entegre etmek istediğinde, tamamen bağımsız iki kod tabanınız olur. Her birinin farklı türde bir Şekil hiyerarşisine sahip olduğunu düşünün. Onları kalıtımla kolayca birleştiremezsiniz ve C şirketini eklersiniz ve büyük bir karmaşa yaşarsınız. Bu konuşmayı izlemeniz gerektiğini düşünüyorum: youtube.com/watch?v=0I0FD3N5cgM Cevabım daha eski, ancak benzerlikleri göreceksiniz. Her şeyi her zaman yeniden uygulamak zorunda değilsiniz, arayüzde bir uygulama sağlayabilir ve varsa bir üye işlevi seçebilirsiniz.
gnzlbg

1
Videonun bir bölümünü izledim ve bu tamamen yanlış. Hata ayıklama amaçları dışında asla dynamic_cast kullanmıyorum. Dinamik yayın, tasarımınızla ilgili yanlış bir şey olduğu ve bu videodaki tasarımların tasarımla yanlış olduğu anlamına gelir :). Guy bile Qt'den bahsediyor, ama burada bile yanlış - QLayout QWidget'ten veya başka bir yoldan miras almıyor!
doc

9

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.


4
Neden, oh neden, kimse bu durumda dtoru saf sanal yapmak ister ki? Bunun kazancı ne olurdu? Türetilmiş sınıflara, bir dtor eklemesi gerekmeyecek bir şeyi zorlarsınız.
Johann Gerell

6
Sorunuzu cevaplamak için cevabım güncellendi. Saf sanal yıkıcı, tüm yöntemlerin varsayılan uygulamalara sahip olduğu bir arabirim sınıfını elde etmenin geçerli bir yoludur (başarmanın tek yolu?).
Rodyland

7

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 .


4
Neden novtablestandart üzerinde kullandığınızı pek anlamıyorumvirtual void Bar() = 0;
Flekso

2
Buna ek olarak (eklediğim eksikleri fark ettim = 0;). Anlamıyorsanız, belgeleri okuyun.
Mark Ingram

Ben olmadan okudum = 0;ve tam olarak aynı yapmanın standart olmayan bir yolu olduğunu varsaydı.
Flekso

4

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.


Sanal kalıtımdan hoşlanıyorum çünkü kavramsal olarak kalıtsal sınıfın yalnızca bir örneği var demektir. Kuşkusuz, buradaki sınıfın herhangi bir yer ihtiyacı yoktur, bu yüzden gereksiz olabilir. Bir süredir C ++ 'da MI yapmadım, ancak sanal olmayan kalıtım, upcasting'i zorlaştırmaz mı?
Uri

Neden, oh neden, kimse bu durumda dtoru saf sanal yapmak ister ki? Bunun kazancı ne olurdu? Türetilmiş sınıflara, bir dtor eklemesi gerekmeyecek bir şeyi zorlarsınız.
Johann Gerell

2
Bir nesnenin arabirime bir işaretçi aracılığıyla imha edileceği bir durum varsa
Uri

Saf bir sanal yıkıcıda yanlış bir şey yok. Gerekli değil, ama bununla ilgili yanlış bir şey yok. Türetilmiş bir sınıfta yıkıcı uygulamak, o sınıfın uygulayıcısı üzerinde neredeyse büyük bir yük değildir. Bunu neden yapacağınız için aşağıdaki cevabımı görün.
Rodyland

Sanal kalıtım için +1, çünkü arayüzlerde sınıfın iki veya daha fazla yoldan arayüz türetmesi daha olasıdır. Ben arayüzlerde korumalı yıkıcılar tercih tho.
doc

4

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.
};

Jim Hyslop ve Herb Sutter'in bu "Dobations: Neredeyse Sevgiler" başlıklı Dr Dobbs makalesi , neden NVI kullanmak isteyebileceğine dair biraz daha ayrıntılı.
user2067021

Ve ayrıca Herb Sutter'in bu makalesi "Sanallık".
user2067021

1

C ++ geliştirmede hala yeniyim. Visual Studio (VS) ile başladım.

Ancak, hiç kimse __interfaceVS (.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.


0

virtualBir 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ı:

  • Bunu temel sınıf olarak devralabilir (hem sanal hem de sanal olmayanlara izin verilir) ve clicksoyundan gelen yapıcıyı doldurabilirsiniz .
  • İşlev işaretçisine protectedüye olarak sahip olabilir ve bir publicreferans ve / veya alıcıya sahip olabilirsiniz.
  • Yukarıda belirtildiği gibi, bu, çalışma zamanında uygulamayı değiştirmenizi sağlar. Dolayısıyla bu devleti de yönetmenin bir yoludur. ifKodunuzdaki 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()ifif
  • Seçerseniz 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).

0

İşte abstract classc ++ 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.


-2
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.


5
Bir arayüz olarak kabul edilen bu değil! Bu sadece geçersiz kılınması gereken bir yöntemle soyut bir temel sınıf! Arabirimler genellikle yalnızca yöntem tanımlarını içeren nesnelerdir - diğer sınıflar arabirimi uygularken yerine getirmeleri gereken bir "sözleşme".
guitarflow
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.