¿Cómo declaras una interfaz en C ++?

¿Cómo configuro una clase que representa una interfaz? ¿Es solo una clase base abstracta?

    Para ampliar la respuesta de bradtgmurray , puede hacer una excepción a la lista de métodos virtuales puros de su interfaz agregando un destructor virtual. Esto le permite pasar la propiedad del puntero a otra parte sin exponer la clase derivada concreta. El destructor no tiene que hacer nada, porque la interfaz no tiene ningún miembro concreto. Puede parecer contradictorio definir una función como virtual y en línea, pero créanme, no lo es.

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

    No es necesario que incluya un cuerpo para el destructor virtual: algunos comstackdores tienen problemas para optimizar un destructor vacío y es mejor utilizar el predeterminado.

    Haz una clase con métodos virtuales puros. Use la interfaz creando otra clase que anule esos métodos virtuales.

    Un método virtual puro es un método de clase que se define como virtual y se asigna a 0.

     class IDemo { public: virtual ~IDemo() {} virtual void OverrideMe() = 0; }; class Child : public IDemo { public: virtual void OverrideMe() { //do stuff } }; 

    La razón por la que tiene una categoría de tipo de interfaz especial además de las clases base abstractas en C # / Java es porque C # / Java no admite herencia múltiple.

    C ++ admite herencia múltiple, por lo que no se necesita un tipo especial. Una clase base abstracta sin métodos no abstractos (puros virtuales) es funcionalmente equivalente a una interfaz C # / Java.

    No existe un concepto de “interfaz” per se en C ++. AFAIK, las interfaces se introdujeron por primera vez en Java para evitar la herencia múltiple. Este concepto ha resultado ser bastante útil, y se puede lograr el mismo efecto en C ++ utilizando una clase base abstracta.

    Una clase base abstracta es una clase en la que al menos una función miembro (método en jerga Java) es una función virtual pura declarada utilizando la siguiente syntax:

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

    No se puede crear una instancia de una clase base abstracta, es decir, no puede declarar un objeto de clase A. Solo puede derivar clases de A, pero cualquier clase derivada que no proporcione una implementación de foo() también será abstracta. Para dejar de ser abstracto, una clase derivada debe proporcionar implementaciones para todas las funciones virtuales puras que hereda.

    Tenga en cuenta que una clase base abstracta puede ser más que una interfaz, ya que puede contener miembros de datos y funciones de miembros que no son puramente virtuales. Un equivalente de una interfaz sería una clase base abstracta sin ningún dato con solo funciones virtuales puras.

    Y, como señaló Mark Ransom, una clase base abstracta debería proporcionar un destructor virtual, al igual que cualquier clase base, para el caso.

    Hasta donde pude probar, es muy importante agregar el destructor virtual. Estoy usando objetos creados con new y destruido con delete .

    Si no agrega el destructor virtual en la interfaz, no se llama al destructor de la clase heredada.

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

    Si ejecuta el código anterior sin virtual ~IBase() {}; , verá que el destructor Tester::~Tester() nunca se llama.

    Mi respuesta es básicamente la misma que las otras, pero creo que hay otras dos cosas importantes que hacer:

    1. Declare un destructor virtual en su interfaz o IDemo uno no virtual protegido para evitar comportamientos indefinidos si alguien intenta eliminar un objeto de tipo IDemo .

    2. Use herencia virtual para evitar problemas con herencia múltiple. (Hay más herencia múltiple cuando usamos interfaces).

    Y como otras respuestas:

    • Haz una clase con métodos virtuales puros.
    • Use la interfaz creando otra clase que anule esos métodos virtuales.

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

      O

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

      Y

       class Child : virtual public IDemo { public: virtual void OverrideMe() { //do stuff } } 

    Todas las buenas respuestas anteriores. Una cosa más que debes tener en cuenta es que también puedes tener un destructor virtual puro. La única diferencia es que todavía necesita implementarlo.

    ¿Confuso?

     --- header file ---- class foo { public: foo() {;} virtual ~foo() = 0; virtual bool overrideMe() {return false;} }; ---- source ---- foo::~foo() { } 

    La razón principal por la que desea hacer esto es si desea proporcionar métodos de interfaz, como yo, pero puede anularlos opcionalmente.

    Para hacer que la clase sea una clase de interfaz requiere un método virtual puro, pero todos sus métodos virtuales tienen implementaciones predeterminadas, por lo que el único método que queda para hacer el virtual puro es el destructor.

    Reimplementar un destructor en la clase derivada no es gran cosa: siempre reimplante un destructor, virtual o no, en mis clases derivadas.

    En C ++ 11 puedes evitar fácilmente la herencia por completo:

     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 explicit Interface(T& other) : foo([=](){ return other.foo(); }), bar([=](){ return other.bar(); }), /*...*/ {} const std::function foo; const std::function bar; // ... }; 

    En este caso, una interfaz tiene semántica de referencia, es decir, debe asegurarse de que el objeto sobrevive a la interfaz (también es posible crear interfaces con semántica de valores).

    Este tipo de interfaces tienen sus pros y sus contras:

    • Requieren más memoria que el polymorphism basado en la herencia.
    • En general, son más rápidos que el polymorphism basado en la herencia.
    • En los casos en que conoces el tipo final, ¡ son mucho más rápidos! (algunos comstackdores como gcc y clang realizan más optimizaciones en tipos que no tienen / heredan de tipos con funciones virtuales).

    Finalmente, la herencia es la raíz de todo mal en el diseño de software complejo. En el Semantics Value and Concepts-based Polymorphism de Sean Parent (altamente recomendado, se explican mejores versiones de esta técnica) se estudia el siguiente caso:

    Digamos que tengo una aplicación en la que trato mis formas polimórficamente usando la interfaz MyShape :

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

    En su aplicación, usted hace lo mismo con diferentes formas usando la interfaz YourShape :

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

    Ahora di que quieres usar algunas de las formas que he desarrollado en tu aplicación. Conceptualmente, nuestras formas tienen la misma interfaz, pero para que mis formas funcionen en su aplicación, necesitaría ampliar mis formas de la siguiente manera:

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

    En primer lugar, la modificación de mis formas podría no ser posible en absoluto. Además, la herencia múltiple lidera el camino hacia el código de spaghetti (imagínese que viene un tercer proyecto que está utilizando la interfaz TheirShape … ¿qué sucede si también llaman a su función draw my_draw ?).

    Actualización: hay un par de referencias nuevas sobre el polymorphism no heredado:

    • La herencia de Sean Parent es la clase base de conversación malvada .
    • Sean Value ‘s Value-semántica y polymorphism basado en conceptos .
    • Charla de polymorphism libre de herencia de Pyry Jahkola y los documentos de la biblioteca poli .
    • Borrado de tipo pragmático de Zach Laine : Resolviendo problemas de OOP con un patrón de diseño elegante .
    • Blog de Andrzej en C ++ – Escriba las partes de borrado i , ii , iii y iv .
    • Tiempo de ejecución genérico polimórfico genérico: mezcla de objetos y conceptos en ConceptC ++
    • Boost.TypeErasure docs
    • Documentos de Adobe Poly
    • Boost.Any , std :: cualquier propuesta (revisión 3) , Boost.Spirit :: hold_any .

    Si está utilizando el comstackdor de C ++ de Microsoft, podría hacer lo siguiente:

     struct __declspec(novtable) IFoo { virtual void Bar() = 0; }; class Child : public IFoo { public: virtual void Bar() override { /* Do Something */ } } 

    Me gusta este enfoque porque da como resultado un código de interfaz mucho más pequeño y el tamaño del código generado puede ser significativamente menor. El uso de novtable elimina toda referencia al puntero vtable en esa clase, por lo que nunca puede instanciarlo directamente. Ver la documentación aquí – novtable .

    Una pequeña adición a lo escrito allí:

    Primero, asegúrate de que tu destructor también sea puramente virtual

    En segundo lugar, es posible que desee heredar de forma virtual (en lugar de normalmente) cuando lo implemente, solo por buenas medidas.

    También puede considerar clases de contrato implementadas con el NVI (Non Virtual Interface Pattern). Por ejemplo:

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

    Todavía soy nuevo en el desarrollo de C ++. Empecé con Visual Studio (VS).

    Sin embargo, nadie parece mencionar la __interface en VS (.NET) . No estoy muy seguro de si esta es una buena forma de declarar una interfaz. Pero parece proporcionar una aplicación adicional (mencionada en los documentos ). De modo que no tiene que especificar explícitamente el virtual TYPE Method() = 0; , ya que se convertirá automáticamente.

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

    Sin embargo, no lo uso porque me preocupa la compatibilidad de comstackción multiplataforma, ya que solo está disponible en .NET.

    Si alguien tiene algo interesante al respecto, por favor comparte. 🙂

    Gracias.

    Aquí está la definición de abstract class en c ++ estándar

    n4687

    13.4.2

    Una clase abstracta es una clase que puede usarse solo como una clase base de alguna otra clase; no se pueden crear objetos de una clase abstracta excepto como subobjetos de una clase derivada de ella. Una clase es abstracta si tiene al menos una función virtual pura.

    Si bien es cierto que virtual es el estándar de facto para definir una interfaz, no nos olvidemos del clásico patrón tipo C, que viene con un constructor en C ++:

     struct IButton { void (*click)(); // might be std::function(void()) if you prefer IButton( void (*click_)() ) : click(click_) { } }; // call as: // (button.*click)(); 

    Esto tiene la ventaja de que puede volver a vincular el tiempo de ejecución de los eventos sin tener que construir su clase de nuevo (ya que C ++ no tiene una syntax para cambiar los tipos polimórficos, esta es una solución para las clases de camaleón).

    Consejos:

    • Puede heredar esto como una clase base (se permiten tanto virtuales como no virtuales) y completar el click en el constructor de su descendiente.
    • Puede tener el puntero de función como miembro protected y tener una referencia public y / o getter.
    • Como se mencionó anteriormente, esto le permite cambiar la implementación en tiempo de ejecución. Por lo tanto, es una forma de administrar el estado también. Dependiendo de la cantidad de cambios s vs state en su código, esto podría ser más rápido que switch() es o if s (se espera un cambio de respuesta alrededor de 3-4 if s, pero siempre mida primero.
    • Si elige std::function<> sobre los punteros de función, es posible que pueda gestionar todos sus datos de objeto dentro de IBase . A partir de este punto, puede tener un esquema de valores para IBase (por ejemplo, std::vector funcionará). Tenga en cuenta que esto puede ser más lento según el comstackdor y el código STL; también que las implementaciones actuales de std::function<> tienden a tener una sobrecarga en comparación con punteros de función o incluso funciones virtuales (esto podría cambiar en el futuro).
     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; } 

    Resultado: área del rectángulo: 35 área del triángulo: 17

    Hemos visto cómo una clase abstracta definió una interfaz en términos de getArea () y otras dos clases implementaron la misma función pero con un algoritmo diferente para calcular el área específica de la forma.