virtuales sirve que puras polimorfismo para hoc herencia funciones datos con apuntadores c++ function polymorphism virtual override

c++ - sirve - ¿Puedo obtener un comportamiento polimórfico sin usar funciones virtuales?



polimorfismo en base de datos (10)

Debido a mi dispositivo no puedo usar funciones virtuales. Supongamos que tengo:

class Base { void doSomething() { } }; class Derived : public Base { void doSomething() { } }; // in any place { Base *obj = new Derived; obj->doSomething(); }

el obj->doSomething() llamará solo la Base::doSomething()

¿Hay alguna forma con Base *obj , de llamar al doSomething of the Derived ?

Sé que puedo poner un virtual antes de que doSomething() de Base resuelva el problema, pero mi dispositivo me limita, el compilador no lo admite.


¿Se puede encapsular la clase base en lugar de derivarla?

Entonces puedes llamar a doSomething () // se deriva
o base-> doSomething () // base de llamadas


Claro que puedes hacer esto; Simplemente no es necesariamente fácil.

Si hay una lista finita de clases derivadas y sabe qué son cuando define la clase base, puede hacerlo utilizando un contenedor de función de miembro no polimórfico. Aquí hay un ejemplo con dos clases derivadas. No utiliza las instalaciones de la biblioteca estándar y se basa únicamente en las características estándar de C ++.

class Base; class Derived1; class Derived2; class MemFnWrapper { public: enum DerivedType { BaseType, Derived1Type, Derived2Type }; typedef void(Base::*BaseFnType)(); typedef void(Derived1::*Derived1FnType)(); typedef void(Derived2::*Derived2FnType)(); MemFnWrapper(BaseFnType fn) : type_(BaseType) { fn_.baseFn_ = fn; } MemFnWrapper(Derived1FnType fn) : type_(Derived1Type) {fn_.derived1Fn_ = fn;} MemFnWrapper(Derived2FnType fn) : type_(Derived2Type) {fn_.derived2Fn_ = fn;} void operator()(Base* ptr) const; private: union FnUnion { BaseFnType baseFn_; Derived1FnType derived1Fn_; Derived2FnType derived2Fn_; }; DerivedType type_; FnUnion fn_; }; class Base { public: Base() : doSomethingImpl(&Base::myDoSomething) { } Base(MemFnWrapper::Derived1FnType f) : doSomethingImpl(f) { } Base(MemFnWrapper::Derived2FnType f) : doSomethingImpl(f) { } void doSomething() { doSomethingImpl(this); } private: void myDoSomething() { } MemFnWrapper doSomethingImpl; }; class Derived1 : public Base { public: Derived1() : Base(&Derived1::myDoSomething) { } private: void myDoSomething() { } }; class Derived2 : public Base { public: Derived2() : Base(&Derived2::myDoSomething) { } private: void myDoSomething() { } }; // Complete the MemFnWrapper function call operator; this has to be after the // definitions of Derived1 and Derived2 so the cast is valid: void MemFnWrapper::operator()(Base* ptr) const { switch (type_) { case BaseType: return (ptr->*(fn_.baseFn_))(); case Derived1Type: return (static_cast<Derived1*>(ptr)->*(fn_.derived1Fn_))(); case Derived2Type: return (static_cast<Derived2*>(ptr)->*(fn_.derived2Fn_))(); } } int main() { Base* obj0 = new Base; Base* obj1 = new Derived1; Base* obj2 = new Derived2; obj0->doSomething(); // calls Base::myDoSomething() obj1->doSomething(); // calls Derived1::myDoSomething() obj2->doSomething(); // calls Derived2::myDoSomething() }

(Originalmente sugerí usar std::function , que hace mucho de este trabajo para ti, pero luego recordé que es una envoltura de función polimórfica, por lo que necesariamente usa funciones virtuales. :-P Oops. Puedes ver el historial de revisiones para mira como se veia


Creo que es posible con CRTP (si su ''Dispositivo'' admite plantillas).

#include <iostream> template<class T> struct base{ void g(){ if(T *p = static_cast<T *>(this)){ p->f(); } } void f(){volatile int v = 0; std::cout << 1;} virtual ~base(){} }; struct derived1 : base<derived1>{ void f(){std::cout << 2;} }; struct derived2 : base<derived2>{ void f(){std::cout << 3;} }; int main(){ derived1 d1; d1.g(); derived2 d2; d2.g(); }


Dado que los métodos virtuales se implementan generalmente mediante vtables, no hay magia que no se pueda replicar en el código. De hecho, podría implementar su propio mecanismo de envío virtual. Se necesita algo de trabajo, tanto por parte del programador que implementa la clase base como del programador que implementa la clase derivada, pero funciona.

Lanzar el puntero, como lo sugiere ceretullis, es probablemente lo primero que debes considerar hacer. Pero la solución que publico aquí al menos le da la oportunidad de escribir código que usa estas clases como si su compilador fuera compatible con virtual . Es decir, con una simple función llamada.

Este es un programa que implementa una clase Base con una función que devuelve una string : "base", y una clase Derived que devuelve una string : "der". La idea es poder soportar código como este:

Base* obj = new Der; cout << obj->get_string();

... de modo que la llamada a get_string() devolverá "der" a pesar de que estamos llamando a través de un puntero Base y usando un compilador que no admite virtual .

Funciona mediante la implementación de nuestra propia versión de un vtable. En realidad, no es realmente una mesa. Es solo un puntero de función miembro en la clase base. En la implementación de get_string() la clase base, si el puntero de la función miembro no es nulo, se llama a la función. Si es nulo, se ejecuta la implementación de la clase base.

Sencillo, sencillo y bastante básico. Esto probablemente podría mejorarse mucho. Pero muestra la técnica básica.

#include <cstdlib> #include <string> #include <iostream> using namespace std; class Base { public: typedef string (Base::*vptr_get_string)(void) const; Base(vptr_get_string=0); void set_derived_pointer(Base* derived); string get_string() const; protected: Base* der_ptr_; vptr_get_string get_string_vf_; }; Base::Base(vptr_get_string get_string_vf) : der_ptr_(0), get_string_vf_(get_string_vf) { } void Base::set_derived_pointer(Base* derived) { der_ptr_ = derived; } string Base::get_string() const { if( get_string_vf_ ) return (der_ptr_->*get_string_vf_)(); else return "base"; } class Der : public Base { public: Der(); string get_string() const; }; Der::Der() : Base(static_cast<Base::vptr_get_string>(&Der::get_string)) { set_derived_pointer(this); } string Der::get_string() const { return "der"; } int main() { Base* obj = new Der; cout << obj->get_string(); delete obj; }


Podría convertir el puntero de clase base a la clase derivada y llamar a la función.

Base* obj = new Derived; Derived* d = static_cast<Derived*>( obj ); d->doSomething();

Como doSomething() no se declara virtual , debe obtener la implementación derivada.


Puede bajar el objeto al tipo Derivado y llamarlo, así:

static_cast<Derived*>(obj)->doSomething();

aunque eso no ofrece ninguna garantía de que lo que ''obj'' apunta realmente sea del tipo Derived .

Me preocupa más que ni siquiera tenga acceso a las funciones virtuales. ¿Cómo funcionan los destructores si ninguna de sus funciones puede ser virtual, y usted está subclasificando?


Puede utilizar la plantilla para el polimorfismo en tiempo de compilación .

template<class SomethingDoer> class MyClass { public: void doSomething() {myDoer.doSomething();} private: SomethingDoer myDoer; }; class BaseSomethingDoer { public: void doSomething() { // base implementation } }; class DerivedSomethingDoer { public: void doSomething() { // derived implementation } }; typedef MyClass<BaseSomethingDoer> Base; typedef MyClass<DerivedSomethingDoer> Derived;

Ahora, no podemos apuntar a un puntero Derived con una Base , pero podemos tener funciones de plantilla que tomen un MyClass, y que funcionarán con objetos tanto de Base como Derived .


Puedes hacer tu propia vtable, supongo. Solo sería una estructura que contenga los punteros de su función "virtual" como parte de la Base, y tendría un código para configurar vtable.

Este es un tipo de solución general: el trabajo del compilador de C ++ es manejar esta característica.

Pero aquí va:

#include <iostream> class Base { protected: struct vt { void (*vDoSomething)(void); } vt; private: void doSomethingImpl(void) { std::cout << "Base doSomething" << std::endl; } public: void doSomething(void) { (vt.vDoSomething)();} Base() : vt() { vt.vDoSomething = (void(*)(void)) &Base::doSomethingImpl;} }; class Derived : public Base { public: void doSomething(void) { std::cout << "Derived doSomething" << std::endl; } Derived() : Base() { vt.vDoSomething = (void(*)(void)) &Derived::doSomething;} };


Simplemente no hay una forma sencilla de hacer esto sin métodos virtuales.


Mi primera respuesta muestra que, de hecho, es posible obtener al menos una forma limitada de comportamiento polimórfico sin depender realmente del soporte del lenguaje para el polimorfismo.

Sin embargo, ese ejemplo tiene una enorme cantidad de repetitivo. Ciertamente, no se escalaría bien: para cada clase que agregue, tiene que modificar seis lugares diferentes en el código, y para cada función miembro que quiera apoyar, necesita duplicar la mayor parte de ese código. Puaj

Bueno, buenas noticias: con la ayuda del preprocesador (y la biblioteca de preprocesadores Boost, por supuesto), podemos extraer fácilmente la mayor parte de esa placa y hacer que esta solución sea manejable.

Para sacar la placa de calderas del camino, necesitarás estas macros. Puede ponerlos en un archivo de encabezado y olvidarse de ellos si lo desea; Son bastante genéricos. [Por favor, no huyas después de leer esto; Si no está familiarizado con la biblioteca de preprocesadores Boost.probablemente, se ve aterrador :-) Después de este primer bloque de código, veremos cómo podemos usar esto para hacer que nuestro código de aplicación sea mucho más limpio. Si lo desea, puede ignorar los detalles de este código.]

El código se presenta en el orden en el que está porque si copia y pasa cada uno de los bloques de código de esta publicación, en orden, en un archivo fuente de C ++, se compilará y ejecutará.

He llamado a esto la "Biblioteca pseudo-polimórfica"; cualquier nombre que comience con "PseudoPM", con cualquier mayúscula, debe ser considerado como reservado. Las macros que comienzan con PSEUDOPM son macros públicamente invocables; Las macros que comienzan con PSEUDOPMX son para uso interno.

#include <boost/preprocessor.hpp> // [INTERNAL] PSEUDOPM_INIT_VTABLE Support #define PSEUDOPMX_INIT_VTABLE_ENTRY(r, c, i, fn) / BOOST_PP_COMMA_IF(BOOST_PP_NOT_EQUAL(0, i)) / & c :: BOOST_PP_CAT(BOOST_PP_TUPLE_ELEM(4, 0, fn), Impl) // [INTERNAL] PSEUDOPM_DECLARE_VTABLE Support #define PSEUDOPMX_DECLARE_VTABLE_STRUCT_MEMBER(r, c, i, fn) / BOOST_PP_TUPLE_ELEM(4, 1, fn) / (c :: * BOOST_PP_CAT(BOOST_PP_TUPLE_ELEM(4, 0, fn), Ptr)) / BOOST_PP_TUPLE_ELEM(4, 3, fn); #define PSEUDOPMX_DECLARE_VTABLE_STRUCT(r, memfns, c) / struct BOOST_PP_CAT(PseudoPMIntVTable, c) / { / friend class c; / BOOST_PP_SEQ_FOR_EACH_I(PSEUDOPMX_DECLARE_VTABLE_STRUCT_MEMBER, c, memfns)/ }; #define PSEUDOPMX_DECLARE_VTABLE_ENUM_MEMBER(r, x, i, c) / BOOST_PP_COMMA_IF(BOOST_PP_NOT_EQUAL(0, i)) BOOST_PP_CAT(PseudoPMType, c) #define PSEUDOPMX_DECLARE_VTABLE_UNION_MEMBER(r, x, c) / BOOST_PP_CAT(PseudoPMIntVTable, c) BOOST_PP_CAT(BOOST_PP_CAT(table_, c), _); #define PSEUDOPMX_DECLARE_VTABLE_RESET_FN(r, x, c) / void Reset(BOOST_PP_CAT(PseudoPMIntVTable, c) table) / { / type_ = BOOST_PP_CAT(PseudoPMType, c); / table_.BOOST_PP_CAT(BOOST_PP_CAT(table_, c), _) = table; / } #define PSEUDOPMX_DECLARE_VTABLE_PUBLIC_FN(r, x, fn) / BOOST_PP_TUPLE_ELEM(4, 1, fn) / BOOST_PP_TUPLE_ELEM(4, 0, fn) / BOOST_PP_TUPLE_ELEM(4, 3, fn); // [INTERNAL] PSEUDOPM_DEFINE_VTABLE Support #define PSEUDOPMX_DEFINE_VTABLE_ARGLIST0 #define PSEUDOPMX_DEFINE_VTABLE_ARGLIST1 a0 #define PSEUDOPMX_DEFINE_VTABLE_ARGLIST2 a0, a1 #define PSEUDOPMX_DEFINE_VTABLE_ARGLIST3 a0, a1, a2 #define PSEUDOPMX_DEFINE_VTABLE_ARGLIST4 a0, a1, a2, a3 #define PSEUDOPMX_DEFINE_VTABLE_ARGLIST5 a0, a1, a2, a3, a4 #define PSEUDOPMX_DEFINE_VTABLE_ARGLIST6 a0, a1, a2, a3, a4, a5 #define PSEUDOPMX_DEFINE_VTABLE_ARGLIST7 a0, a1, a2, a3, a4, a5, a6 #define PSEUDOPMX_DEFINE_VTABLE_ARGLIST8 a0, a1, a2, a3, a4, a5, a6, a7 #define PSEUDOPMX_DEFINE_VTABLE_ARGLIST9 a0, a1, a2, a3, a4, a5, a6, a7, a8 #define PSEUDOPMX_DEFINE_VTABLE_FNP(r, x, i, t) / BOOST_PP_COMMA_IF(BOOST_PP_NOT_EQUAL(0, i)) / t BOOST_PP_CAT(a, i) #define PSEUDOPMX_DEFINE_VTABLE_FN_CASE(r, fn, i, c) / case BOOST_PP_CAT(PseudoPMType, c) : return / ( / static_cast<c*>(this)->*pseudopm_vtable_.table_. / BOOST_PP_CAT(BOOST_PP_CAT(table_, c), _). / BOOST_PP_CAT(BOOST_PP_TUPLE_ELEM(4, 0, fn), Ptr) / )( / BOOST_PP_CAT( / PSEUDOPMX_DEFINE_VTABLE_ARGLIST, / BOOST_PP_TUPLE_ELEM(4, 2, fn) / ) / ); #define PSEUDOPMX_DEFINE_VTABLE_FN(r, classes, fn) / BOOST_PP_TUPLE_ELEM(4, 1, fn) / BOOST_PP_SEQ_HEAD(classes) :: BOOST_PP_TUPLE_ELEM(4, 0, fn) / ( / BOOST_PP_SEQ_FOR_EACH_I( / PSEUDOPMX_DEFINE_VTABLE_FNP, x, / BOOST_PP_TUPLE_TO_SEQ( / BOOST_PP_TUPLE_ELEM(4, 2, fn), / BOOST_PP_TUPLE_ELEM(4, 3, fn) / ) / ) / ) / { / switch (pseudopm_vtable_.type_) / { / BOOST_PP_SEQ_FOR_EACH_I(PSEUDOPMX_DEFINE_VTABLE_FN_CASE, fn, classes) / } / } // Each class in the classes sequence should call this macro at the very // beginning of its constructor. ''c'' is the name of the class for which // to initialize the vtable, and ''memfns'' is the member function sequence. #define PSEUDOPM_INIT_VTABLE(c, memfns) / BOOST_PP_CAT(PseudoPMIntVTable, c) pseudopm_table = / { / BOOST_PP_SEQ_FOR_EACH_I(PSEUDOPMX_INIT_VTABLE_ENTRY, c, memfns) / }; / pseudopm_vtable_.Reset(pseudopm_table); // The base class should call this macro in its definition (at class scope). // This defines the virtual table structs, enumerations, internal functions, // and declares the public member functions. ''classes'' is the sequence of // classes and ''memfns'' is the member function sequence. #define PSEUDOPM_DECLARE_VTABLE(classes, memfns) / protected: / BOOST_PP_SEQ_FOR_EACH(PSEUDOPMX_DECLARE_VTABLE_STRUCT, memfns, classes) / / enum PseudoPMTypeEnum / { / BOOST_PP_SEQ_FOR_EACH_I(PSEUDOPMX_DECLARE_VTABLE_ENUM_MEMBER, x, classes) / }; / / union PseudoPMVTableUnion / { / BOOST_PP_SEQ_FOR_EACH(PSEUDOPMX_DECLARE_VTABLE_UNION_MEMBER, x, classes) / }; / / class PseudoPMVTable / { / public: / BOOST_PP_SEQ_FOR_EACH(PSEUDOPMX_DECLARE_VTABLE_RESET_FN, x, classes) / private: / friend class BOOST_PP_SEQ_HEAD(classes); / PseudoPMTypeEnum type_; / PseudoPMVTableUnion table_; / }; / / PseudoPMVTable pseudopm_vtable_; / / public: / BOOST_PP_SEQ_FOR_EACH(PSEUDOPMX_DECLARE_VTABLE_PUBLIC_FN, x, memfns) // This macro must be called in some source file after all of the classes in // the classes sequence have been defined (so, for example, you can create a // .cpp file, include all the class headers, and then call this macro. It // actually defines the public member functions for the base class. Each of // the public member functions calls the correct member function in the // derived class. ''classes'' is the sequence of classes and ''memfns'' is the // member function sequence. #define PSEUDOPM_DEFINE_VTABLE(classes, memfns) / BOOST_PP_SEQ_FOR_EACH(PSEUDOPMX_DEFINE_VTABLE_FN, classes, memfns)

(Deberíamos hacer que el vtable sea estático, pero lo dejaré como un ejercicio para el lector. :-D)

Ahora que está fuera del camino, podemos ver lo que necesita hacer en su aplicación para usar esto.

Primero, necesitamos definir la lista de clases que van a estar en nuestra jerarquía de clases:

// The sequence of classes in the class hierarchy. The base class must be the // first class in the sequence. Derived classes can be in any order. #define CLASSES (Base)(Derived)

En segundo lugar, necesitamos definir la lista de funciones miembro "virtuales". Tenga en cuenta que con esta implementación (es decir, limitada), la clase base y cada clase derivada deben implementar cada una de las funciones miembro "virtuales". Si una clase no define uno de estos, el compilador se enojará.

// The sequence of "virtual" member functions. Each entry in the sequence is a // four-element tuple: // (1) The name of the function. A function will be declared in the Base class // with this name; it will do the dispatch. All of the classes in the class // sequence must implement a private implementation function with the same // name, but with "Impl" appended to it (so, if you declare a function here // named "Foo" then each class must define a "FooImpl" function. // (2) The return type of the function. // (3) The number of arguments the function takes (arity). // (4) The arguments tuple. Its arity must match the number specified in (3). #define VIRTUAL_FUNCTIONS / ((FuncNoArg, void, 0, ())) / ((FuncOneArg, int, 1, (int))) / ((FuncTwoArg, int, 2, (int, int)))

Tenga en cuenta que puede nombrar estas dos macros como desee; Solo tendrás que actualizar las referencias en los siguientes fragmentos.

A continuación, podemos definir nuestras clases. En la clase base, necesitamos llamar a PSEUDOPM_DECLARE_VTABLE para declarar las funciones de miembro virtual y definir todo el boilerplate para nosotros. En todos nuestros constructores de clase, necesitamos llamar a PSEUDOPM_INIT_VTABLE ; esta macro genera el código requerido para inicializar el vtable correctamente.

En cada clase también debemos definir todas las funciones miembro que enumeramos anteriormente en la secuencia VIRTUAL_FUNCTIONS . Tenga en cuenta que debemos nombrar las implementaciones con un sufijo Impl ; esto se debe a que las implementaciones siempre se PSEUDOPM_DECLARE_VTABLE través de las funciones del distribuidor generadas por la macro PSEUDOPM_DECLARE_VTABLE .

class Base { public: Base() { PSEUDOPM_INIT_VTABLE(Base, VIRTUAL_FUNCTIONS) } PSEUDOPM_DECLARE_VTABLE(CLASSES, VIRTUAL_FUNCTIONS) private: void FuncNoArgImpl() { } int FuncOneArgImpl(int x) { return x; } int FuncTwoArgImpl(int x, int y) { return x + y; } }; class Derived : public Base { public: Derived() { PSEUDOPM_INIT_VTABLE(Derived, VIRTUAL_FUNCTIONS) } private: void FuncNoArgImpl() { } int FuncOneArgImpl(int x) { return 2 * x; } int FuncTwoArgImpl(int x, int y) { return 2 * (x + y); } };

Finalmente, en algún archivo fuente, deberá incluir todos los encabezados donde se definen todas las clases y llamar a la macro PSEUDOPM_DEFINE_VTABLE ; esta macro en realidad define las funciones del despachador. Esta macro no se puede utilizar si todas las clases aún no se han definido (tiene que static_cast la clase base this puntero, y esto fallará si el compilador no sabe que la clase derivada se deriva realmente de la clase base).

PSEUDOPM_DEFINE_VTABLE(CLASSES, VIRTUAL_FUNCTIONS)

Aquí hay un código de prueba que demuestra la funcionalidad:

#include <cassert> int main() { Base* obj0 = new Base; Base* obj1 = new Derived; obj0->FuncNoArg(); // calls Base::FuncNoArg obj1->FuncNoArg(); // calls Derived::FuncNoArg assert(obj0->FuncTwoArg(2, 10) == 12); // Calls Base::FuncTwoArg assert(obj1->FuncTwoArg(2, 10) == 24); // Calls Derived::FuncTwoArg }

[Descargo de responsabilidad: este código sólo se ha probado parcialmente. Puede contener errores. (De hecho, probablemente lo haga; escribí la mayor parte a la 1 am de esta mañana :-P)]