¿Cómo configuro una clase que representa una interfaz? ¿Es esto solo una clase base abstracta?
¿Cómo configuro una clase que representa una interfaz? ¿Es esto solo una clase base abstracta?
Respuestas:
Para ampliar la respuesta de 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 tanto virtual como 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 usar el predeterminado.
=0
destructor virtual ( ) puro con un cuerpo. La ventaja aquí es que el compilador puede, en teoría, ver que vtable no tiene miembros válidos ahora y descartarlo por completo. Con un destructor virtual con un cuerpo, dicho destructor se puede llamar (virtualmente), por ejemplo, en el medio de la construcción mediante un this
puntero (cuando el objeto construido todavía es de Parent
tipo), y por lo tanto el compilador debe proporcionar una tabla válida. Entonces, si no llama explícitamente a los destructores virtuales this
durante la construcción :), puede ahorrar en el tamaño del código.
override
palabra clave para permitir el argumento en tiempo de compilación y la comprobación del tipo de valor de retorno. Por ejemplo, en la declaración de Niñovirtual void OverrideMe() override;
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
}
};
override
en C ++ 11
Toda 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 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.
Thread
instancia. La herencia múltiple puede ser un mal diseño, así como la composición. Todo depende del caso.
No existe el concepto de "interfaz" per se en C ++. AFAIK, las interfaces se introdujeron por primera vez en Java para evitar la falta de herencia múltiple. Este concepto ha resultado ser bastante útil, y se puede lograr el mismo efecto en C ++ mediante el uso de una clase base abstracta.
Una clase base abstracta es una clase en la que al menos una función miembro (método en jerga de Java) es una función virtual pura declarada utilizando 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 virtuales. Un equivalente de una interfaz sería una clase base abstracta sin ningún dato con solo funciones virtuales puras.
Y, como Mark Ransom señaló, 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 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 Tester::~Tester()
nunca se llama al destructor .
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 cree uno no virtual protegido para evitar comportamientos indefinidos si alguien intenta eliminar un objeto de tipo IDemo
.
Use la herencia virtual para evitar problemas con la herencia múltiple. (Con mayor frecuencia hay herencia múltiple cuando usamos interfaces).
Y como otras respuestas:
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
}
}
En C ++ 11 puede 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 sobreviva a la interfaz (también es posible hacer interfaces con semántica de valor).
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 la semántica de valor de Sean Parent y el 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 mis formas polimórficamente usando la MyShape
interfaz:
struct MyShape { virtual void my_draw() = 0; };
struct Circle : MyShape { void my_draw() { /* ... */ } };
// more shapes: e.g. triangle
En su aplicación, hace lo mismo con diferentes formas utilizando la YourShape
interfaz:
struct YourShape { virtual void your_draw() = 0; };
struct Square : YourShape { void your_draw() { /* ... */ } };
/// some more shapes here...
Ahora digamos que desea usar algunas de las formas que he desarrollado en su aplicación. Conceptualmente, nuestras formas tienen la misma interfaz, pero para que mis formas funcionen en su aplicación, necesitaría extender mis formas de la siguiente manera:
struct Circle : MyShape, YourShape {
void my_draw() { /*stays the same*/ };
void your_draw() { my_draw(); }
};
Primero, modificar mis formas podría no ser posible en absoluto. Además, la herencia múltiple conduce al camino hacia el código de espagueti (imagine que viene un tercer proyecto que usa elTheirShape
interfaz ... ¿qué sucede si también llaman a su función de dibujo my_draw
?).
Actualización: Hay un par de nuevas referencias sobre el polimorfismo basado en la no herencia:
Circle
clase es un diseño pobre. Debe usar el Adapter
patrón en tales casos. Perdón si suena un poco duro, pero trata de usar alguna biblioteca de la vida real como Qt
antes de hacer juicios sobre la herencia. La herencia hace la vida mucho más fácil.
Adapter
patrón? Estoy interesado en ver sus ventajas.
Square
que ya no está allí? ¿Presciencia? Por eso está separado de la realidad. Y, en realidad, si elige confiar en la biblioteca "MyShape", puede adoptar su interfaz desde el principio. En el ejemplo de formas hay muchas tonterías (una de las cuales es que tiene dos Circle
estructuras), pero el adaptador se vería así -> ideone.com/UogjWk
Todas las buenas respuestas anteriores. Una cosa adicional que debe tener en cuenta: también puede tener un destructor virtual puro. La única diferencia es que aún 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 desearía hacer esto es si desea proporcionar métodos de interfaz, como yo lo he hecho, pero hacer que la anulación sea opcional.
Para convertir la clase en una clase de interfaz se 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, siempre reimplemento un destructor, virtual o no, en mis clases derivadas.
Si está utilizando el compilador C ++ de Microsoft, puede 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 más pequeño. El uso de novtable elimina toda referencia al puntero vtable en esa clase, por lo que nunca puede crear una instancia directamente. Vea la documentación aquí - novtable .
novtable
más de lo estándarvirtual void Bar() = 0;
= 0;
que falta que he agregado). Lea la documentación si no la comprende.
= 0;
y asumí que era solo una forma no estándar de hacer exactamente lo mismo.
Una pequeña adición a lo que está escrito allí:
Primero, asegúrese de que su destructor también sea puramente virtual
En segundo lugar, es posible que desee heredar virtualmente (en lugar de normalmente) cuando implemente, solo por buenas medidas.
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.
};
Todavía soy nuevo en el desarrollo de C ++. Empecé con Visual Studio (VS).
Sin embargo, nadie parece mencionar el __interface
en VS (.NET) . Soy no muy seguro de si esto es una buena manera de declarar una interfaz. Pero parece proporcionar una aplicación adicional (mencionada en los documentos ). De modo que no tenga 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, compártelo. :-)
Gracias.
Si bien es cierto que virtual
es el estándar de facto para definir una interfaz, no olvidemos el 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 nuevamente (como C ++ no tiene una sintaxis para cambiar los tipos polimórficos, esta es una solución alternativa para las clases de camaleón).
Consejos:
click
el constructor de su descendiente.protected
miembro y tener unpublic
referencia y / o captador.if
cambios de s vs. estado en su código, esto podría ser más rápido que switch()
es o if
s (se espera un cambio de alrededor de 3-4if
s, pero siempre mida primero).std::function<>
sobre punteros de función, es posible que pueda administrar todos los datos de sus objetos IBase
. Desde este punto, puede tener esquemas de valor para IBase
(por ejemplo, std::vector<IBase>
funcionará). Tenga en cuenta que esto puede ser más lento dependiendo de su compilador y código STL; también que las implementaciones actuales std::function<>
tienden a tener una sobrecarga en comparación con los punteros de función o incluso las funciones virtuales (esto podría cambiar en el futuro).Aquí está la definición de abstract class
en estándar c ++
n4687
13.4.2
Una clase abstracta es una clase que solo se puede usar como clase base de 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.
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.