c++ - puede - programacion de interfaces
¿Cómo se declara una interfaz en C++? (15)
Aquí está la definición de abstract class
en el estándar de c ++
n4687
13.4.2
Una clase abstracta es una clase que se puede usar 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.
¿Cómo configuro una clase que representa una interfaz? ¿Es esto solo una clase base abstracta?
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 una semántica de referencia, es decir, debe asegurarse de que el objeto supere la interfaz (también es posible crear interfaces con semántica de valor).
Este tipo de interfaces tienen sus pros y sus contras:
- Requieren más memoria que el polimorfismo basado en herencia.
- En general, son más rápidos que el polimorfismo basado en la herencia.
- En aquellos casos en los que conoces el tipo final, ¡ son mucho más rápidos! (algunos compiladores 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 Semántico de valor de Sean Parent y Polimorfismo basado en conceptos (muy recomendable, se explican mejores versiones de esta técnica) se estudia el siguiente caso:
Digamos que tengo una aplicación en la que trato con mis formas de forma polimórfica usando la interfaz MyShape
:
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 la interfaz de YourShape
:
struct YourShape { virtual void your_draw() = 0; };
struct Square : YourShape { void your_draw() { /* ... */ } };
/// some more shapes here...
Ahora diga que desea utilizar algunas de las formas que he desarrollado en su aplicación. Conceptualmente, nuestras formas tienen la misma interfaz, pero para hacer que mis formas funcionen en tu aplicación, necesitarías extender 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 lleva el camino hacia el código de espagueti (imagina que viene un tercer proyecto que usa la interfaz TheirShape
... ¿qué sucede si también llaman a su función de dibujo my_draw
?).
Actualización: hay un par de referencias nuevas sobre el polimorfismo no basado en herencia:
- La herencia de Sean Parent es la clase base de las malas conversaciones.
- Discusión sobre la semántica de valores de Sean Parent y el polimorfismo basado en conceptos .
- La charla sobre el polimorfismo sin herencia de Pyry Jahkola y los documentos de la biblioteca poli .
- Borrado de tipo pragmático de Zach Laine : Resolución de problemas de OOP con un diseño elegante Hablar de patrón .
- Blog de C ++ de Andrzej: escriba las partes de Borrado i , ii , iii y iv .
- Programación genérica polimórfica en tiempo de ejecución: 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 .
Hacer 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 principal 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 la 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 (virtuales puros) es funcionalmente equivalente a una interfaz C # / Java.
Mi respuesta es básicamente la misma que las otras, pero creo que hay otras dos cosas importantes que hacer:
Declare un destructor virtual en su interfaz o haga uno protegido no virtual para evitar comportamientos indefinidos si alguien intenta eliminar un objeto de tipo
IDemo
.Utilice la herencia virtual para evitar problemas con la herencia múltiple. (Es más frecuente que haya herencia múltiple cuando usamos interfaces).
Y como otras respuestas:
- Hacer 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 } }
No hay concepto de "interfaz" per se en C ++. AFAIK, las interfaces se introdujeron por primera vez en Java para solucionar la falta de 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 la jerga de Java) es una función virtual pura declarada con la siguiente sintaxis:
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 la 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 datos con solo funciones virtuales puras.
Y, como señaló Mark Ransom, una clase base abstracta debe proporcionar un destructor virtual, al igual que cualquier clase base, para el caso.
Para ampliar la respuesta mediante 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 miembros concretos. Puede parecer contradictorio definir una función como virtual o en línea, pero confía en mí, 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 tiene que incluir un cuerpo para el destructor virtual: resulta que algunos compiladores tienen problemas para optimizar un destructor vacío y es mejor utilizar el valor predeterminado.
Por lo que pude probar, es muy importante agregar el destructor virtual. Estoy usando objetos creados con new
y destruidos 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.
Si bien es cierto que virtual
es el estándar de facto para definir una interfaz, no olvidemos el patrón clásico de 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 los eventos en tiempo de ejecución sin tener que construir su clase nuevamente (ya que C ++ no tiene una sintaxis para cambiar los tipos polimórficos, esta es una solución para las clases de camaleón).
Consejos:
- Puede heredar de esto como una clase base (se permite tanto virtual como no virtual) y completar el
click
en el constructor de su descendiente. - Puede tener el puntero a la función como miembro
protected
y tener una referenciapublic
y / o captador. - 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
if
s vs. cambios de estado en su código, esto podría ser más rápido que losswitch()
sswitch()
oif
s (se espera un giro alrededor de 3-4if
s, pero siempre mida primero). - Si elige
std::function<>
sobre los punteros de función, es posible que pueda administrar todos sus datos de objeto dentro deIBase
. Desde este punto, puede tener esquemas de valores paraIBase
(por ejemplo,std::vector<IBase>
funcionará). Tenga en cuenta que esto podría ser más lento dependiendo de su compilador y código STL; también que las implementaciones actuales destd::function<>
tienden a tener una sobrecarga cuando se comparan con los punteros de función o incluso las funciones virtuales (esto podría cambiar en el futuro).
Si está utilizando el compilador C ++ de Microsoft, entonces 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 todas las referencias al puntero vtable en esa clase, por lo que nunca puede instanciarlo directamente. Vea la documentación aquí - novtable .
También puede considerar las clases de contrato implementadas con el NVI (patrón de interfaz no virtual). 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.
};
Todas las buenas respuestas anteriores. Una cosa adicional que debe tener en cuenta es que también puede tener un destructor virtual puro. La única diferencia es que todavía necesitas 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 querría hacer esto es si desea proporcionar métodos de interfaz, como lo he hecho yo, pero haga que la anulación sea opcional.
Para hacer de la clase, 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 virtual puro es el destructor.
Reimplementar un destructor en la clase derivada no es gran cosa en absoluto: siempre reimplemento un destructor, virtual o no, en mis clases derivadas.
Todavía soy nuevo en el desarrollo de C ++. Comencé con Visual Studio (VS).
Sin embargo, nadie parece mencionar la __interface
en VS (.NET) . No estoy muy seguro de si esta es una buena manera 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 compilación multiplataforma, ya que solo está disponible en .NET.
Si alguien tiene algo interesante al respecto, por favor comparta. :-)
Gracias.
Una pequeña adición a lo que está escrito allí:
Primero, asegúrate de que tu destructor sea también virtual puro
En segundo lugar, es posible que desee heredar virtualmente (en lugar de normalmente) cuando lo implementa, solo por buenas medidas.
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: Rectángulo área: 35 Área de 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.