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


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


728
2017-11-25 16:48


origen


Respuestas:


Para ampliar la respuesta por bradtgmurray, es posible que desee 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 compiladores tienen problemas para optimizar un destructor vacío y es mejor utilizar el predeterminado.


625
2017-11-25 17:11



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

226
2017-11-25 16:53



Toda la razón por la que tiene una categoría de tipo de interfaz especial además de 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.


130
2017-11-25 17:01



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 sintaxis:

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

Una clase base abstracta no puede ser instanciada, i. mi. 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á abstracto 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.


43
2017-11-25 17:27



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ás que el destructor Tester::~Tester()nunca es llamado.


38
2018-03-05 17:53



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

31
2017-11-25 18:48



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.


9
2017-11-25 22:02



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

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:

Finalmente, la herencia es la raíz de todo mal en el diseño de software complejo. En Sean Parent's Value Semántica y polimorfismo basado en conceptos (Muy 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 el MyShape interfaz:

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

En tu aplicación, haces lo mismo con diferentes formas usando el YourShape interfaz:

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á usando el TheirShape interfaz ... qué pasa si también llaman a su función de dibujar my_draw?).

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


8
2018-06-25 13:51