Jak zadeklarować interfejs w C ++?

Odpowiedzi:

686

Aby rozwinąć odpowiedź bradtgmurray , możesz zrobić jeden wyjątek od listy czysto wirtualnych metod interfejsu, dodając wirtualny destruktor. Umożliwia to przekazanie własności wskaźnika innej stronie bez ujawnienia konkretnej klasy pochodnej. Destruktor nie musi nic robić, ponieważ interfejs nie ma żadnych konkretnych elementów. Definiowanie funkcji jako wirtualnej i wbudowanej może wydawać się sprzeczne, ale zaufaj mi - nie jest.

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

Nie musisz dołączać ciała do wirtualnego destruktora - okazuje się, że niektóre kompilatory mają problemy z optymalizacją pustego destruktora i lepiej jest użyć domyślnego.

Mark Ransom
źródło
106
Wirtualny desctuctor ++! To jest bardzo ważne. Możesz także dołączyć czystą wirtualną deklarację operatora = i skopiować definicje konstruktora, aby uniemożliwić automatyczne generowanie tych kompilatorów.
xan
33
Alternatywą dla wirtualnego destruktora jest chroniony destruktor. Wyłącza to zniszczenie polimorficzne, które może być bardziej odpowiednie w niektórych okolicznościach. Poszukaj „Wytycznej nr 4” w gotw.ca/publications/mill18.htm .
Fred Larson,
9
Inną opcją jest zdefiniowanie czystego wirtualnego ( =0) destruktora z ciałem. Zaletą jest to, że kompilator może teoretycznie zobaczyć, że vtable nie ma teraz poprawnych elementów, i całkowicie go odrzucić. W przypadku wirtualnego destruktora z ciałem wspomniany destruktor można nazwać (wirtualnie) np. W środku konstrukcji za pomocą thiswskaźnika (gdy obiekt jest nadal Parenttypu), a zatem kompilator musi dostarczyć poprawną tabelę vt. Więc jeśli nie wywołujesz jawnie wirtualnych destruktorów thispodczas budowy :) możesz zaoszczędzić na rozmiarze kodu.
Pavel Minaev
51
Jak typowe dla odpowiedzi w C ++ jest to, że najwyższa odpowiedź nie odpowiada bezpośrednio na pytanie (choć oczywiście kod jest idealny), ale optymalizuje prostą odpowiedź.
Tim
18
Nie zapominaj, że w C ++ 11 możesz określić overridesłowo kluczowe, aby umożliwić sprawdzanie argumentów czasu kompilacji i zwracanie typu wartości. Na przykład w deklaracji Childvirtual void OverrideMe() override;
Sean
244

Utwórz klasę za pomocą czysto wirtualnych metod. Użyj interfejsu, tworząc inną klasę, która zastąpi te wirtualne metody.

Metoda czysto wirtualna to metoda klasy zdefiniowana jako wirtualna i przypisana do 0.

class IDemo
{
    public:
        virtual ~IDemo() {}
        virtual void OverrideMe() = 0;
};

class Child : public IDemo
{
    public:
        virtual void OverrideMe()
        {
            //do stuff
        }
};
bradtgmurray
źródło
29
powinieneś mieć niszczyciel nic nie rób w IDemo, aby zdefiniować zachowanie: IDemo * p = new Child; / * cokolwiek * / usuń p;
Evan Teran,
11
Dlaczego metoda OverrideMe w klasie Child jest wirtualna? Czy to konieczne?
Cemre
9
@Cemre - nie, nie jest to konieczne, ale też nie boli.
PowerApp101
11
Zasadniczo dobrym pomysłem jest zachowanie słowa kluczowego „wirtualnego” za każdym razem, gdy przesłania się metodę wirtualną. Chociaż nie jest to wymagane, może uczynić kod jaśniejszym - w przeciwnym razie nie ma żadnych wskazówek, że ta metoda mogłaby być zastosowana polimorficznie lub nawet istnieje w klasie bazowej.
Kevin
27
@Kevin Z wyjątkiem overridew C ++ 11
keyser
146

Cały powód, dla którego oprócz abstrakcyjnych klas bazowych w C # / Java masz specjalną kategorię typów interfejsów, to fakt, że C # / Java nie obsługuje wielokrotnego dziedziczenia.

C ++ obsługuje wielokrotne dziedziczenie, więc specjalny typ nie jest potrzebny. Abstrakcyjna klasa bazowa bez metod nieabstrakcyjnych (czysto wirtualnych) jest funkcjonalnie równoważna interfejsowi C # / Java.

Joel Coehoorn
źródło
17
Byłoby miło móc tworzyć interfejsy, aby zaoszczędzić nam tyle pisania (virtual, = 0, virtual destructor). Również wielokrotne dziedziczenie wydaje mi się naprawdę złym pomysłem i nigdy nie widziałem, aby było używane w praktyce, ale interfejsy są potrzebne przez cały czas. Szkoda, że ​​środowisko C ++ nie wprowadza interfejsów tylko dlatego, że ich chcę.
Zdobył
9
Ha11owed: Ma interfejsy. Nazywa się je klasami z czysto wirtualnymi metodami i bez implementacji metod.
Miles Rout
6
@doc: java.lang.Thread ma metody i stałe, których prawdopodobnie nie chcesz mieć w swoim obiekcie. Co powinien zrobić kompilator, jeśli wykonasz rozszerzenie z Thread i innej klasy za pomocą publicznej metody checkAccess ()? Czy naprawdę wolałbyś używać silnie nazwanych wskaźników bazowych jak w C ++? Wygląda to na zły projekt, zwykle potrzebujesz kompozycji, w której uważasz, że potrzebujesz wielokrotnego dziedziczenia.
Zdobył
4
@ Wiedziałem, że to było dawno temu, więc nie pamiętam szczegółów, ale miałem metody i elementy, które chciałem mieć w swojej klasie, a co ważniejsze, chciałem, aby mój pochodny obiekt klasy był Threadinstancją. Wielokrotne dziedziczenie może być złym projektem, a także kompozycją. Wszystko zależy od przypadku.
dok.
2
@Dave: Naprawdę? Cel C ma ocenę czasu kompilacji i szablony?
Deduplicator
51

W C ++ nie ma pojęcia „interfejsu” jako takiego. AFAIK, interfejsy zostały po raz pierwszy wprowadzone w Javie, aby obejść brak wielokrotnego dziedziczenia. Ta koncepcja okazała się całkiem użyteczna, a ten sam efekt można osiągnąć w C ++, używając abstrakcyjnej klasy bazowej.

Abstrakcyjna klasa podstawowa to klasa, w której co najmniej jedna funkcja składowa (metoda w języku Java Lingo) jest czystą funkcją wirtualną zadeklarowaną przy użyciu następującej składni:

class A
{
  virtual void foo() = 0;
};

Nie można utworzyć instancji abstrakcyjnej klasy bazowej, tzn. Nie można zadeklarować obiektu klasy A. Klasy można wywodzić tylko z A, ale każda klasa pochodna, która nie zapewnia implementacji foo(), również będzie abstrakcyjna. Aby przestać być abstrakcyjnym, klasa pochodna musi zapewniać implementacje dla wszystkich dziedziczonych funkcji czysto wirtualnych.

Należy pamiętać, że abstrakcyjna klasa podstawowa może być czymś więcej niż interfejsem, ponieważ może zawierać elementy danych i funkcje elementów, które nie są czysto wirtualne. Odpowiednikiem interfejsu byłaby abstrakcyjna klasa bazowa bez danych posiadająca wyłącznie funkcje wirtualne.

I, jak zauważył Mark Ransom, abstrakcyjna klasa podstawowa powinna zapewniać wirtualny destruktor, podobnie jak każda inna klasa podstawowa.

Dima
źródło
13
Bardziej niż „brak wielokrotnego dziedziczenia” powiedziałbym, aby zastąpić wielokrotne dziedziczenie. Java została zaprojektowana w ten sposób od samego początku, ponieważ wielokrotne dziedziczenie stwarza więcej problemów niż to, co rozwiązuje. Dobra odpowiedź
OscarRyz
11
Oscar, to zależy od tego, czy jesteś programistą C ++, który nauczył się języka Java lub odwrotnie. :) IMHO, jeśli jest używane rozważnie, jak prawie wszystko w C ++, wielokrotne dziedziczenie rozwiązuje problemy. Abstrakcyjna klasa bazowa „interfejsu” jest przykładem bardzo rozsądnego zastosowania wielokrotnego dziedziczenia.
Dima,
8
@OscarRyz Wrong. MI tworzy problem tylko w przypadku niewłaściwego użycia. Większość domniemanych problemów z MI pojawiłaby się również w alternatywnych projektach (bez MI). Kiedy ludzie mają problem z ich projektem z MI, to wina MI; jeśli mają problem projektowy z SI, to z własnej winy. „Diament śmierci” (powtarzające się dziedzictwo) jest tego doskonałym przykładem. Walenie z MI nie jest czystą hipokryzją, ale bliską.
ciekawy,
4
Semantycznie interfejsy różnią się od klas abstrakcyjnych, więc interfejsy Javy nie są tylko technicznym obejściem. Wybór między zdefiniowaniem interfejsu lub klasy abstrakcyjnej zależy od semantyki, a nie od względów technicznych. Wyobraźmy sobie interfejs „HasEngine”: jest to aspekt, cecha i może być zastosowany / zaimplementowany przez bardzo różne typy (klasy lub klasy abstrakcyjne), więc zdefiniujemy dla tego interfejs, a nie klasę abstrakcyjną.
Marek Stanley,
2
@MarekStanley, możesz mieć rację, ale chciałbym, żebyś wybrał lepszy przykład. Lubię myśleć o tym w kategoriach dziedziczenia interfejsu vs. dziedziczenia implementacji. W C ++ możesz albo dziedziczyć interfejs, jak i implementację razem (dziedziczenie publiczne) lub możesz dziedziczyć tylko implementację (dziedziczenie prywatne). W Javie masz możliwość dziedziczenia samego interfejsu, bez implementacji.
Dima,
43

O ile mogłem przetestować, bardzo ważne jest dodanie wirtualnego destruktora. Używam obiektów utworzonych newi zniszczonych przy pomocy delete.

Jeśli nie dodasz wirtualnego destruktora w interfejsie, wówczas destruktor odziedziczonej klasy nie zostanie wywołany.

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

Jeśli uruchomisz poprzedni kod bez virtual ~IBase() {};, zobaczysz, że destruktor Tester::~Tester()nigdy nie jest wywoływany.

Carlos C Soto
źródło
3
Najlepsza odpowiedź na tej stronie, ponieważ ma sens, podając praktyczny, możliwy do kompilacji przykład. Twoje zdrowie!
Lumi,
1
Testet :: ~ Tester () działa tylko wtedy, gdy obj jest „Deklarowane za pomocą Testera”.
Alessandro L.,
W rzeczywistości wywołany zostanie destruktor ciągu nazwy prywatnej łańcucha, a w pamięci to wszystko, na co zostanie przydzielone. Jeśli chodzi o środowisko wykonawcze, gdy wszystkie konkretne elementy klasy są niszczone, podobnie jak instancja klasy. Próbowałem podobnego eksperymentu z klasą Line, która miała dwie struktury Point i odkryłem, że obie struktury zostały zniszczone (Ha!) Po wywołaniu delete lub powrocie z funkcji otaczającej. valgrind potwierdził 0 przeciek.
Chris Reid
33

Moja odpowiedź jest w zasadzie taka sama jak inne, ale myślę, że są dwie inne ważne rzeczy do zrobienia:

  1. Zadeklaruj wirtualny destruktor w swoim interfejsie lub stwórz chroniony nie-wirtualny, aby uniknąć niezdefiniowanych zachowań, jeśli ktoś spróbuje usunąć obiekt typu IDemo.

  2. Użyj wirtualnego dziedziczenia, aby uniknąć problemów z wielokrotnym dziedziczeniem. (Kiedy korzystamy z interfejsów, częściej występuje wielokrotne dziedziczenie).

I podobnie jak inne odpowiedzi:

  • Utwórz klasę za pomocą czysto wirtualnych metod.
  • Użyj interfejsu, tworząc inną klasę, która zastąpi te wirtualne metody.

    class IDemo
    {
        public:
            virtual void OverrideMe() = 0;
            virtual ~IDemo() {}
    }
    

    Lub

    class IDemo
    {
        public:
            virtual void OverrideMe() = 0;
        protected:
            ~IDemo() {}
    }
    

    I

    class Child : virtual public IDemo
    {
        public:
            virtual void OverrideMe()
            {
                //do stuff
            }
    }
    
Rexxar
źródło
2
nie ma potrzeby dziedziczenia wirtualnego, ponieważ w interfejsie nie ma żadnych elementów danych.
Robocide
3
Dziedziczenie wirtualne jest również ważne dla metod. Bez niego wpadniesz w dwuznaczności w OverrideMe (), nawet jeśli jedno z jego „wystąpień” jest czysto wirtualne (sam tego próbowałem).
Knarf Navillus
5
@Avishay_ ” nie ma potrzeby wirtualnego dziedziczenia, ponieważ nie ma żadnych członków danych w interfejsie. ” Nieprawidłowe.
ciekawy,
Zwróć uwagę, że dziedziczenie wirtualne może nie działać w niektórych wersjach gcc, jak w wersji 4.3.3, która jest dostarczana z WinAVR 2010: gcc.gnu.org/bugzilla/show_bug.cgi?id=35067
mMontu
-1 za posiadanie nie-wirtualnego chronionego destruktora, przepraszam
Wolf
10

W C ++ 11 można łatwo całkowicie uniknąć dziedziczenia:

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

W takim przypadku interfejs ma semantykę odniesienia, tzn. Musisz upewnić się, że obiekt przeżywa interfejs (możliwe jest także tworzenie interfejsów z semantyką wartości).

Tego rodzaju interfejsy mają swoje zalety i wady:

Wreszcie, dziedziczenie jest źródłem wszelkiego zła w złożonym projektowaniu oprogramowania. W Sean Parent's Value Semantyka i polimorfizm oparty na pojęciach (wysoce zalecane, wyjaśniono tam lepsze wersje tej techniki) badany jest następujący przypadek:

Załóżmy, że mam aplikację, w której radzę sobie z kształtami polimorficznie za pomocą MyShapeinterfejsu:

struct MyShape { virtual void my_draw() = 0; };
struct Circle : MyShape { void my_draw() { /* ... */ } };
// more shapes: e.g. triangle

W swojej aplikacji robisz to samo z różnymi kształtami za pomocą YourShapeinterfejsu:

struct YourShape { virtual void your_draw() = 0; };
struct Square : YourShape { void your_draw() { /* ... */ } };
/// some more shapes here...

Teraz powiedz, że chcesz użyć niektórych kształtów, które opracowałem w Twojej aplikacji. Koncepcyjnie, nasze kształty mają ten sam interfejs, ale aby moje kształty działały w Twojej aplikacji, musisz rozszerzyć moje kształty w następujący sposób:

struct Circle : MyShape, YourShape { 
  void my_draw() { /*stays the same*/ };
  void your_draw() { my_draw(); }
};

Po pierwsze, modyfikowanie moich kształtów może być w ogóle niemożliwe. Co więcej, wielokrotne dziedziczenie prowadzi do kodu spaghetti (wyobraź sobie, że pojawia się trzeci projekt korzystający z TheirShapeinterfejsu ... co się stanie, jeśli wywołają również funkcję rysowania my_draw?).

Aktualizacja: Istnieje kilka nowych odniesień na temat polimorfizmu opartego na braku dziedziczenia:

gnzlbg
źródło
5
Dziedziczenie TBH jest znacznie bardziej przejrzyste niż ta C ++ 11, która udaje interfejs, ale jest raczej klejem do łączenia niektórych niespójnych projektów. Przykład kształtów jest oderwany od rzeczywistości, a Circleklasa to kiepski projekt. W Adaptertakich przypadkach powinieneś użyć wzoru. Przepraszam, jeśli zabrzmi to trochę ostro, ale spróbuj użyć biblioteki z prawdziwego życia, tak jak Qtprzed osądzeniem o spadku. Dziedziczenie znacznie ułatwia życie.
dok.
2
To wcale nie brzmi ostro. Jak oddzielić przykład kształtu od rzeczywistości? Czy możesz podać przykład (być może na ideonie) naprawy Koła za pomocą Adapterwzoru? Chcę zobaczyć jego zalety.
gnzlbg
OK, postaram się zmieścić w tym małym pudełku. Przede wszystkim zwykle wybierasz biblioteki takie jak „MyShape”, zanim zaczniesz pisać własną aplikację, aby zabezpieczyć swoją pracę. W przeciwnym razie skąd możesz wiedzieć, że jeszcze go Squarenie ma? Czy znasz? Właśnie dlatego jest oderwany od rzeczywistości. W rzeczywistości, jeśli zdecydujesz się na bibliotekę „MyShape”, możesz dostosować się do jej interfejsu od samego początku. W przykładzie kształtów jest wiele bzdur (jednym z nich jest to, że masz dwie Circlestruktury), ale adapter wyglądałby mniej więcej
doc
2
Wtedy nie jest oderwany od rzeczywistości. Kiedy firma A kupuje firmę B i chce zintegrować bazę kodu firmy B z A, masz dwie całkowicie niezależne bazy kodu. Wyobraź sobie, że każda z nich ma hierarchię kształtów różnych typów. Nie można ich łatwo łączyć z dziedziczeniem, dodać firmę C i masz wielki bałagan. Myślę, że powinieneś obejrzeć ten wykład: youtube.com/watch?v=0I0FD3N5cgM Moja odpowiedź jest starsza, ale zobaczysz podobieństwa. Nie musisz cały czas reimplementować, możesz zapewnić implementację w interfejsie i wybrać funkcję członka, jeśli jest dostępna.
gnzlbg
1
Obejrzałem część filmu i to jest całkowicie błędne. Nigdy nie używam dynamic_cast z wyjątkiem celów debugowania. Dynamiczna obsada oznacza, że ​​coś jest nie tak z twoim projektem, a projekty w tym filmie są błędne z założenia :). Facet wspomina nawet o Qt, ale nawet tutaj się myli - QLayout nie dziedziczy po QWidget ani na odwrót!
dok.
9

Wszystkie dobre odpowiedzi powyżej. Jedną dodatkową rzeczą, o której powinieneś pamiętać - możesz też mieć czysty wirtualny destruktor. Jedyną różnicą jest to, że nadal musisz ją wdrożyć.

Zmieszany?


    --- header file ----
    class foo {
    public:
      foo() {;}
      virtual ~foo() = 0;

      virtual bool overrideMe() {return false;}
    };

    ---- source ----
    foo::~foo()
    {
    }

Głównym powodem, dla którego chcesz to zrobić, jest udostępnienie metod interfejsu, tak jak ja, ale zastąpienie ich opcjonalnym.

Aby klasa stała się klasą interfejsu, wymaga czystej metody wirtualnej, ale wszystkie metody wirtualne mają domyślne implementacje, więc jedyną metodą na stworzenie czystej wirtualnej jest destruktor.

Reimplementacja destruktora w klasie pochodnej nie jest niczym wielkim - zawsze reimplementuję destruktor, wirtualny lub nie, w moich klasach pochodnych.

Rodyland
źródło
4
Dlaczego, och dlaczego, ktoś chciałby uczynić dtor w tym przypadku czystym wirtualnym? Jaki byłby tego zysk? Po prostu wymusiłbyś na klasach pochodnych coś, czego prawdopodobnie nie muszą zawierać - dtor.
Johann Gerell
6
Zaktualizowałem moją odpowiedź, aby odpowiedzieć na twoje pytanie. Czysty wirtualny destruktor jest prawidłowym sposobem na osiągnięcie (jedyny sposób na osiągnięcie?) Klasy interfejsu, w której wszystkie metody mają domyślne implementacje.
Rodyland,
7

Jeśli używasz kompilatora C ++ firmy Microsoft, możesz wykonać następujące czynności:

struct __declspec(novtable) IFoo
{
    virtual void Bar() = 0;
};

class Child : public IFoo
{
public:
    virtual void Bar() override { /* Do Something */ }
}

Podoba mi się to podejście, ponieważ powoduje znacznie mniejszy kod interfejsu, a generowany rozmiar kodu może być znacznie mniejszy. Zastosowanie novtable usuwa wszystkie odwołania do wskaźnika vtable w tej klasie, więc nigdy nie można bezpośrednio utworzyć jego instancji. Zobacz dokumentację tutaj - nowatorską .

Mark Ingram
źródło
4
Nie do końca rozumiem, dlaczego użyłeś novtablestandarduvirtual void Bar() = 0;
Flexo
2
Jest to dodatek do (właśnie zauważyłem brakujące, = 0;które dodałem). Przeczytaj dokumentację, jeśli jej nie rozumiesz.
Mark Ingram,
Przeczytałem go bez = 0;i zakładałem, że był to po prostu niestandardowy sposób robienia dokładnie tego samego.
Flexo
4

Mały dodatek do tego, co tam jest napisane:

Po pierwsze, upewnij się, że twój destruktor jest również czysto wirtualny

Po drugie, możesz chcieć odziedziczyć wirtualnie (zamiast normalnie) po wdrożeniu, tylko dla dobrych środków.

Uri
źródło
Lubię wirtualne dziedziczenie, ponieważ koncepcyjnie oznacza to, że istnieje tylko jedna instancja dziedziczonej klasy. Trzeba przyznać, że klasa tutaj nie wymaga żadnego miejsca, więc może być zbędna. Od jakiegoś czasu nie robiłem MI w C ++, ale czy dziedziczenie pozawirusowe nie skomplikowałoby upcastingu?
Uri
Dlaczego, och dlaczego, ktoś chciałby uczynić dtor w tym przypadku czystym wirtualnym? Jaki byłby tego zysk? Po prostu wymusiłbyś na klasach pochodnych coś, czego prawdopodobnie nie muszą zawierać - dtor.
Johann Gerell
2
Jeśli zdarzy się sytuacja, że ​​obiekt zostanie zniszczony przez wskaźnik do interfejsu, należy upewnić się, że destruktor jest wirtualny ...
Uri
Nie ma nic złego w czystym wirtualnym destruktorze. Nie jest to konieczne, ale nie ma w tym nic złego. Implementacja destruktora w klasie pochodnej nie stanowi dużego obciążenia dla implementatora tej klasy. Zobacz moją odpowiedź poniżej, dlaczego to zrobiłeś.
Rodyland,
+1 dla wirtualnego dziedziczenia, ponieważ w przypadku interfejsów jest bardziej prawdopodobne, że klasa wyprowadzi interfejs z dwóch lub więcej ścieżek. Wybieram chronione destruktory w interfejsach, które.
dok.
4

Możesz także rozważyć klasy kontraktów zaimplementowane przy użyciu NVI (Non Virtual Interface Pattern). Na przykład:

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.
};
Luc Hermitte
źródło
Dla innych czytelników ten artykuł dr Dobbs „Conversations: Virtually Yours” autorstwa Jima Hyslopa i Herb'a Suttera wyjaśnia nieco więcej, dlaczego ktoś może chcieć skorzystać z NVI.
user2067021
A także ten artykuł „Virtuality” autorstwa Herb Sutter.
user2067021
1

Nadal jestem nowy w rozwoju C ++. Zacząłem od Visual Studio (VS).

Jednak wydaje się, że nikt nie wspomniał __interfacew VS (.NET) . Ja nie bardzo pewna, czy jest to dobry sposób, aby zadeklarować interfejs. Wydaje się jednak, że zapewnia dodatkowe egzekwowanie (wspomniane w dokumentach ). Tak, abyś nie musiał jawnie określać virtual TYPE Method() = 0;, ponieważ zostanie on automatycznie przekonwertowany.

__interface IMyInterface {
   HRESULT CommitX();
   HRESULT get_X(BSTR* pbstrName);
};

Jednak nie używam go, ponieważ martwię się o kompatybilność kompilacji między platformami, ponieważ jest ona dostępna tylko w .NET.

Jeśli ktoś ma coś ciekawego na ten temat, prosimy o udostępnienie. :-)

Dzięki.

Yeo
źródło
0

Chociaż prawdą virtualjest, że jest to de facto standard definiujący interfejs, nie zapominajmy jednak o klasycznym wzorcu podobnym do C, który jest dostarczany z konstruktorem w C ++:

struct IButton
{
    void (*click)(); // might be std::function(void()) if you prefer

    IButton( void (*click_)() )
    : click(click_)
    {
    }
};

// call as:
// (button.*click)();

Ma to tę zaletę, że można ponownie powiązać środowisko wykonawcze zdarzeń bez konieczności ponownego tworzenia klasy (ponieważ C ++ nie ma składni do zmiany typów polimorficznych, jest to obejście dla klas kameleonów).

Wskazówki:

  • Możesz dziedziczyć po tym jako klasę podstawową (dozwolone są zarówno wirtualne, jak i nie-wirtualne) i wypełnić clickkonstruktora potomka.
  • Możesz mieć wskaźnik funkcji jako element protectedczłonkowski i mieć publicodniesienie i / lub getter.
  • Jak wspomniano powyżej, umożliwia to przełączanie implementacji w czasie wykonywania. Dlatego jest to również sposób na zarządzanie stanem. W zależności od liczby ifzmian stanu w kodzie w porównaniu do stanu s, może to być szybsze niż switch()es lub ifs (zmiana jest oczekiwana około 3-4 ifs, ale zawsze mierz najpierw.
  • Jeśli zdecydujesz się std::function<>na wskaźniki funkcji, może być w stanie zarządzać wszystkimi danych obiektu wewnątrz IBase. Od tego momentu możesz mieć schematy wartości IBase(np. std::vector<IBase>Będą działać). Zauważ, że może to być wolniejsze w zależności od twojego kompilatora i kodu STL; także, że obecne implementacje std::function<>mają zwykle narzut, w porównaniu do wskaźników funkcji, a nawet funkcji wirtualnych (może się to zmienić w przyszłości).
lorro
źródło
0

Oto definicja abstract classstandardu c ++

n4687

13.4.2

Klasa abstrakcyjna to klasa, której można używać tylko jako klasę podstawową innej klasy; nie można tworzyć żadnych obiektów klasy abstrakcyjnej, z wyjątkiem podobiektów pochodnej klasy. Klasa jest abstrakcyjna, jeśli ma co najmniej jedną czystą funkcję wirtualną.

陳 力
źródło
-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;
}

Wynik: obszar prostokąta: 35 obszar trójkąta: 17

Widzieliśmy, jak klasa abstrakcyjna zdefiniowała interfejs w kategoriach getArea (), a dwie inne klasy zaimplementowały tę samą funkcję, ale z innym algorytmem do obliczania obszaru specyficznego dla kształtu.

jego
źródło
5
To nie jest interfejs. To tylko abstrakcyjna klasa podstawowa z jedną metodą, którą należy zastąpić! Interfejsy to zazwyczaj obiekty, które zawierają tylko definicje metod - „kontrakt”, który inne klasy muszą spełnić, gdy implementują interfejs.
Guitarflow