Jak zadeklarować interfejs w C++?

Jak skonfigurować klasę, która reprezentuje interfejs? Czy to tylko abstrakcyjna klasa bazowa?

Author: Peter Mortensen, 2008-11-25

15 answers

Aby rozwinąć odpowiedź przez bradtgmurray , możesz zrobić jeden wyjątek od listy czystych metod wirtualnych interfejsu, dodając wirtualny Destruktor. Pozwala to na przekazanie własności wskaźnika innej stronie bez ujawniania konkretnej klasy pochodnej. Destruktor nie musi nic robić, ponieważ interfejs nie ma żadnych konkretnych członków. Definiowanie funkcji jako wirtualnej i inline może wydawać się sprzeczne, ale zaufaj mi - to 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.

 633
Author: Mark Ransom,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:10:45

Stwórz klasę za pomocą czystych metod wirtualnych. Użyj interfejsu, tworząc inną klasę, która nadpisuje te wirtualne metody.

Czysta metoda wirtualna jest metodą klasy, która jest 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
        }
};
 226
Author: bradtgmurray,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-08-24 16:58:38

Oprócz abstrakcyjnych klas bazowych w C#/ Java istnieje specjalna kategoria typu interfejsu, ponieważ C#/Java nie obsługuje dziedziczenia wielokrotnego.

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

 131
Author: Joel Coehoorn,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-11-28 14:40:35

W C++nie ma pojęcia "interfejsu" jako takiego. AFAIK, interfejsy zostały po raz pierwszy wprowadzone w Javie, aby obejść brak wielokrotnego dziedziczenia. Koncepcja ta okazała się bardzo przydatna, a ten sam efekt można osiągnąć w C++ za pomocą abstrakcyjnej klasy bazowej.

Abstrakcyjna klasa bazowa to klasa, w której co najmniej jedna funkcja składniowa (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;
};

Abstrakcyjna klasa bazowa nie może być instancją, tzn. nie Można zadeklarować obiektu klasy A. można wyprowadzać tylko klasy z A, ale każda pochodna klasa, która nie dostarcza implementacji foo(), będzie również abstrakcyjna. Aby przestac byc abstrakcyjna, klasa pochodna musi zapewnic implementacje dla wszystkich czystych funkcji wirtualnych, które dziedziczy.

Zauważ, że abstrakcyjna klasa bazowa może być czymś więcej niż interfejsem, ponieważ może zawierać elementy składowe danych i funkcje składowe, które nie są czysto wirtualne. Odpowiednik interfejs byłby abstrakcyjną klasą bazową bez żadnych danych z tylko czystymi funkcjami wirtualnymi.

I, jak zauważył Mark Ransom, abstrakcyjna klasa bazowa powinna zapewniać wirtualny Destruktor, tak jak każda klasa bazowa.

 44
Author: Dima,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-07-28 19:56:22

O ile mogłem przetestować, bardzo ważne jest dodanie Wirtualnego destruktora. Używam obiektów utworzonych za pomocą new i zniszczonych za pomocą delete.

Jeśli nie dodasz Wirtualnego destruktora w interfejsie, to 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 zostanie wywołany.

 39
Author: Carlos C Soto,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-07-06 08:20:40

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

  1. Zadeklaruj wirtualny Destruktor w interfejsie lub utwó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 dziedziczeniem wielokrotnym. (Częściej występuje dziedziczenie wielokrotne, gdy używamy interfejsów.)

I jak inne odpowiedzi:

  • Stwórz klasę za pomocą czystych metod wirtualnych.
  • Użyj interfejsu, tworząc inną klasę, która nadpisuje 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
            }
    }
    
 31
Author: Rexxar,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-11-01 18:03:27

Wszystkie dobre odpowiedzi powyżej. Należy pamiętać o jednej dodatkowej rzeczy - możesz również mieć czysty wirtualny Destruktor. Jedyną różnicą jest to, że nadal trzeba go wdrożyć.

Zdezorientowany?

    --- 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 to, że chcesz podać metody interfejsu, tak jak ja, ale aby ich nadpisywanie było opcjonalne.

Aby Klasa stała się klasą interfejsu wymaga czystej metody wirtualnej, ale wszystkie Twoje metody wirtualne mają domyślne implementacje, więc jedyną metodą, która pozwala uczynić czystą wirtualną, jest Destruktor.

Reimplementowanie destruktora w klasie pochodnej to nic wielkiego - zawsze reimplementuję Destruktor, wirtualny lub nie, w klasach pochodnych.

 9
Author: Rodyland,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-12-02 20:26:36

W C++11 można łatwo 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 Tym Przypadku interfejs ma semantykę referencyjną, tzn. trzeba się upewnić, że obiekt przetrwa interfejs (możliwe jest również tworzenie interfejsów z semantyką wartości).

Tego typu interfejsy mają swoje plusy i minusy:

  • wymagają więcej pamięci niż polimorfizmu opartego na dziedziczeniu.
  • One są na ogół szybsze niż dziedziczenie oparte na polimorfizm.
  • w tych przypadkach, w których znasz ostateczny Typ, są znacznie szybsze! (niektóre Kompilatory, takie jak gcc i clang, wykonują więcej optymalizacji w typach, które nie mają / dziedziczą od typów z funkcjami wirtualnymi).

Wreszcie, dziedziczenie jest źródłem wszelkiego zła w skomplikowanym projektowaniu oprogramowania. W semantyka wartości i polimorfizm oparty na pojęciach (wysoce zalecane, lepsze wersje tej techniki są tam wyjaśnione): przypadek jest badany:

Powiedzmy, że mam aplikację, w której zajmuję się swoimi kształtami za pomocą interfejsu MyShape:

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

W Twojej aplikacji robisz to samo z różnymi kształtami za pomocą interfejsu YourShape:

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 jako follows:

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

Po pierwsze, modyfikowanie moich kształtów może nie być w ogóle możliwe. Co więcej, dziedziczenie wielokrotne prowadzi do kodu spaghetti (wyobraź sobie, że pojawia się trzeci projekt, który używa interfejsu TheirShape... co się stanie, jeśli wywołają również swoją funkcję draw my_draw ?).

Aktualizacja: istnieje kilka nowych odniesień o polimorfizmie opartym na dziedziczeniu:

 8
Author: gnzlbg,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 10:31:37

Jeśli używasz kompilatora C++ Microsoftu, 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ż skutkuje to znacznie mniejszym kodem interfejsu, a generowany rozmiar kodu może być znacznie mniejszy. Użycie novtable usuwa wszystkie odniesienia do wskaźnika vtable w tej klasie, więc nigdy nie możesz bezpośrednio utworzyć instancji. Zobacz dokumentację tutaj - novtable .

 7
Author: Mark Ingram,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-10-28 05:40:37

Mały dodatek do tego co tam jest napisane:

Najpierw upewnij się, że Destruktor jest również czysty wirtualny

Po drugie, możesz chcieć dziedziczyć wirtualnie (a nie normalnie) podczas implementacji, tylko dla dobrych środków.

 4
Author: Uri,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-11-25 17:35:34

Można również rozważyć klasy kontraktowe zaimplementowane z 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.
};
 4
Author: Luc Hermitte,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-11-01 14:12:26

Wciąż jestem nowy w C++ development. Zacząłem od Visual Studio (VS).

Jednak wydaje się, że nikt nie wspomniał o __interface W VS (. NET) . nie jestem bardzo pewien, czy jest to dobry sposób na deklarowanie interfejsu. Ale wydaje się, że zapewnia dodatkowe egzekwowanie (wymienione w dokumenty ). Tak, że nie musisz jawnie określać virtual TYPE Method() = 0;, ponieważ zostanie ona automatycznie przekonwertowana.

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

Jednak nie używam go, ponieważ jestem obawy o kompatybilność kompilacji między platformami, ponieważ jest dostępna tylko pod .NET.

Jeśli ktoś ma coś ciekawego na ten temat, proszę się podzielić. :-) Dzięki.
 1
Author: Yeo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-09-27 18:33:01

Chociaż prawdą jest, że virtual jest de facto standardem definiowania interfejsu, nie zapominajmy o klasycznym wzorze 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ć zdarzenia runtime bez konieczności ponownego konstruowania klasy (ponieważ C++ nie ma składni do zmiany typów polimorficznych, jest to obejście dla klas chameleon).

Porady:

  • możesz dziedziczyć z tego jako klasy bazowej (zarówno wirtualne i nie-wirtualne są dozwolone) i wypełnić click w konstruktorze potomka.
  • możesz mieć wskaźnik funkcji jako członek protected i mieć referencję public i / lub getter.
  • Jak wspomniano powyżej, pozwala to na przełączenie implementacji w trybie runtime. Tak więc jest to sposób na zarządzanie państwem, jak również. W zależności od liczby ifS A zmian stanu w Twoim kodzie, to Możebyć szybsze niż switch() es lub ifs (zwrot oczekiwany jest około 3-4 ifs, ale zawsze zmierz pierwszy.
  • jeśli wybierzesz std::function<> zamiast wskaźników funkcji, możesz być w stanie zarządzać wszystkimi danymi obiektu wIBase. Od tego momentu możesz mieć Schematy wartości dla IBase (np. std::vector<IBase> będzie działać). Zauważ, że ta Może być wolniejsza w zależności od Twojego kompilatora i kodu STL; również, że obecne implementacje std::function<> mają tendencję do narzutu w porównaniu ze wskaźnikami funkcji lub nawet funkcjami wirtualnymi (może to zmienić się w przyszłości).
 0
Author: lorro,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-07-26 15:29:25

Oto definicja abstract class w standardzie c++

N4687

13.4.2

Klasa abstrakcyjna jest klasą, która może być używana tylko jako klasa bazowa jakiejś innej klasy; żadne obiekty abstrakcji klasa może być utworzona z wyjątkiem podobiektów klasy z niej pochodzącej. Klasa jest abstrakcyjna, jeśli ma przynajmniej jedna czysta funkcja wirtualna.

 0
Author: 陳 力,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-11-07 01:22:20
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: Powierzchnia prostokąta: 35 Obszar trójkąta: 17

Widzieliśmy, jak abstrakcyjna klasa 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.

 -2
Author: hims,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-12-19 15:45:41