2010-09-29 18 views
14

Debido a mi dispositivo no puedo usar funciones virtuales. Supongamos que tengo:¿Puedo obtener un comportamiento polimórfico sin usar funciones virtuales?

class Base 
{ 
    void doSomething() { } 
}; 

class Derived : public Base 
{ 
    void doSomething() { } 
}; 

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

la obj->doSomething() llamará simplemente la Base::doSomething()

¿Hay alguna manera con Base *obj, para llamar al doSomething del Derived?

sé que sólo puede poner un virtual antes doSomething() de Base se soluciona el problema, pero yo estoy limitado por mi dispositivo, el compilador no lo soporta.

+4

¿Puedo preguntar qué compilador está utilizando?¿Tienes alguna opción de cambiar tu compilador por uno compatible con virtual? – Default

+1

marque como "incrustado" si corresponde. –

+3

Una explicación un poco mayor de lo que * mi dispositivo * sería apropiado. Las funciones virtuales no necesitan soporte de hardware, solo soporte de idiomas. Qué compilador estas usando? ¿Estás seguro de que hay soporte para las clases y la herencia y no el polimorfismo? Usted puede estar ladrando en el árbol equivocado aquí. –

Respuesta

13

Puede bajar el puntero de la clase base a la clase derivada y llamar a la función.

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

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

+4

¿No deberías usar 'dynamic_cast' para echar una base a un derivado? (Soy un novato en C++) – dreamlax

+0

Muy bien. No es divertido, pero funciona. +1 – JoshD

+12

@dreamlax, si las funciones virtuales no funcionan, tampoco es probable que dynamic_cast lo haga. –

0

Simplemente no hay una manera simple de hacer esto sin métodos virtuales.

+3

Estira un poco tu mente. –

+0

@John: Veo que no ha contribuido - o ha sido endosado visiblemente a modo de comentario - cualquier respuesta. De alguna manera, Alexander tiene razón en que el interrogador puede estar al tanto de las posibles soluciones (haciendo un seguimiento manual del tipo derivado y cambiando entre piezas fundidas, manualmente usando indicadores de función) pero preguntando si hay alguna forma inteligente que él no sepa de trabajar con el diseño existente. OMI no hay nada malo en enumerar las cosas de las que puede o no estar al tanto, pero es probable que Alexander no corte la corrida. –

+0

@Tony: Tienes razón, he puesto mi dinero donde está mi boca ahora. –

1

¿Se puede encapsular la clase base en lugar de derivar de ella?

A continuación, puede llamar doSomething() // Se deriva
o base-> doSomething() // base de las llamadas

+0

Entonces no podría mantener un 'Derived' a través de' Base * '; que fue el punto/problema completo. ¿Derecha? –

+0

@ Jon-Eric: En realidad, el objetivo de todo esto es _polimorfismo_, que requiere funciones virtuales ... – sbi

+1

La forma en que se enseña a la mayoría de las personas OO significa que todos piensan que la herencia es la única solución. A menudo la encapsulación es mejor –

6

Puede abatido 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' realmente es del tipo Derived.

Estoy más preocupado de 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á creando subclases?

+4

La pregunta del destructor es buena. –

+4

Con el destructor, solo tiene que tener mucho cuidado de no "eliminar" un objeto de clase derivado a través de un puntero de clase base. –

+1

James McNellis tiene razón sobre la eliminación de una clase derivada, gracias por recordarme. Me pregunto si esta es una limitación real del compilador que está usando, o si el póster original sigue alguna guía o está restringido el uso de funciones virtuales debido a la sobrecarga de despacho dinámico, por razones de rendimiento quizás? – birryree

2

Puede hacer su propia vtable, supongo. Sería una estructura que contiene los punteros de función "virtuales" como parte de Base, y tengo un código para configurar el vtable.

Esto es una especie de gran solución - es el trabajo del compilador de C++ para 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;} 
}; 
+2

Esto produce un comportamiento indefinido; no puede llamar a una función a través de un puntero de función que tiene un tipo que es incompatible con el tipo de la función. –

+1

'Esta es una especie de gran solución: el trabajo del compilador de C++ es manejar esta función. Justo en la marca. El hecho de que el ejemplo invoca involuntariamente a UB y, por lo tanto, agrega una grosería adicional me hace sentir tentado de superarlo en una manera metatarsiana, que rompe la cuarta línea. –

9

Claro que se puede hacer esto; simplemente no es necesariamente fácil.

Si hay una lista finita de clases derivadas y usted sabe cuáles son cuando define la clase base, puede hacerlo utilizando un contenedor de funciones miembro no polimórfico. Aquí hay un ejemplo con dos clases derivadas. No utiliza instalaciones de biblioteca estándar y depende únicamente de 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() 
} 

(que originalmente sugirió el uso de std::function, lo que hace un montón de este trabajo para usted, pero luego recordé que es un envoltorio función polimórfica, por lo que necesariamente se usa funciones virtuales. :-P Vaya.Puede ver el historial de revisiones para ver cómo se veía)

+2

Bonita ilustración James. –

1

Dado que los métodos virtuales generalmente se implementan mediante vtables, no hay magia que no se pueda replicar en el código. De hecho, podría implementar su propio mecanismo de despacho 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 sugiere ceretullis, es probablemente lo primero que debe considerar hacer. Pero la solución que publico aquí al menos le da la oportunidad de escribir código que use estas clases como si su compilador fuera compatible con virtual. Es decir, con una simple llamada de función.

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

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

... para que la llamada get_string() volverá "der" a pesar de que estamos llamando a través de un puntero Base y el uso de un compilador que no es compatible virtual.

Funciona implementando nuestra propia versión de vtable. En realidad, no es realmente una mesa. Es solo un puntero de función de miembro en la clase base. En la implementación de la clase base de get_string(), 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.

Simple, directo 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; 
} 
+0

"no hay magia que no se pueda replicar en el código. De hecho, podría implementar su propio mecanismo de despacho virtual."La declaración anterior amplifica la pregunta que todo el mundo debería haber preguntado sobre esta última: ¿por qué OP pensó que querían evitar las funciones virtuales? Es precisamente la falta de magia y, por cierto, no hay necesidad de RTTI, en caso de que el OP preocupado por eso, eso hace que reinventarlo todo sea absurdo. Aplausos por esfuerzo, pero si alguien quiere polimorfismo pero se ha decidido a no usar funciones virtuales, no estoy seguro de que C++ sea un lenguaje para ellos. –

+0

@underscore_d: OP declaró al principio que no pueden usar virtual 'debido a su dispositivo'. Es muy posible que el sistema para el que estaban codificando no tenga soporte nativo para virtual (la palabra clave, no la funcionalidad). Supuse que ellos no 'decidieron' que no querían usar virtual , pero no tenía acceso a la palabra clave. Esto no es en absoluto absurdo. –

6

My first answer demuestra que sí es posible conseguir por lo menos una forma limitada de comportamiento polimórfico similar sin tener que depender del apoyo del idioma para el polimorfismo.

Sin embargo, ese ejemplo tiene una enorme cantidad de texto estándar. Ciertamente no se escalaría bien: por cada clase que agregue, debe modificar seis lugares diferentes en el código, y para cada función miembro que desee admitir, debe duplicar la mayor parte de ese código. Yuck.

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

Para obtener la plantilla repetitiva, necesitará estas macros. Puede ponerlos en un archivo de cabecera 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 Boost.Preprocessor, probablemente se vea 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 que se encuentra porque si copia y pasa cada uno de los bloques de código de esta publicación, en orden, en una fuente de C++ archivo, lo hará (quiero decir debería!) compilar y ejecutar.

Lo he llamado la "Biblioteca pseudo-polimórfica"; cualquier nombre que comience con "PseudoPM", con mayúsculas, se debe considerar reservado. Las macros que comienzan con PSEUDOPM son macros que se pueden llamar públicamente; 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) 

(Debemos hacer estática la viable, pero lo dejo como ejercicio para el lector. :-D)

Ahora que eso está fuera del camino, en realidad podemos mirar qué necesitas hacer en tu aplicación para usar esto.

En primer lugar, tenemos que 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, hay que definir la lista de funciones miembro "virtuales". Tenga en cuenta que con esta implementación (ciertamente limitada), la clase base y todas las clases derivadas 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á que actualizar las referencias en los siguientes fragmentos.

A continuación, podemos definir nuestras clases. En la clase base, necesitamos llamar al PSEUDOPM_DECLARE_VTABLE para declarar las funciones de miembros virtuales y definir todo el modelo para nosotros. En todos nuestros constructores de clase, debemos llamar al 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 de los miembros 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 invocan a través de las funciones de despachador 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); } 
}; 

Por último, en algún archivo fuente, se tendrá que incluir todas las cabeceras donde se definen todas las clases y llamar a la macro PSEUDOPM_DEFINE_VTABLE; esta macro realmente define las funciones del despachador. Esta macro no se puede usar si todas las clases aún no se han definido (tiene que static_cast el puntero de clase base this, 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 se prueba sólo parcialmente. Puede contener errores. (De hecho, probablemente sí; escribí la mayor parte a la 1 am de esta mañana :-P)]

+0

lol, OP nunca explicó por qué ellos (creen que) no pueden usar virtuales, pero lo aceptaste en la fe y les mostraste cómo implementar un manual spaghetti de funciones virtuales en lugar del nativo. Eso es ... una especie de dedicación extraña.: P Apoya el esfuerzo, pero cualquiera que piense que necesita hacer esto debe replantearse, o admitir que está en el lugar equivocado y solo use C, tal vez con un marco de objetos preexistente. Eventualmente se darán cuenta de que no hay una razón válida para evitar los virtuales, pero es demasiado tarde ... solo pueden ver su código base y soñar lo que podría haber sido, si hubieran preguntado la pregunta correcta. –

0

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

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 señalar a un DerivedBase con un puntero, pero podemos tener funciones de plantilla que se llevan en un MiClase, y que trabajará con ambos BaseDerived y objetos.

+0

"no podemos apuntar a un' Derived' con un poi 'Base' nter ", y sin embargo, esa capacidad es exactamente lo que pidió el OP. El polimorfismo estático es una herramienta fantástica, pero no es relevante aquí. Ver también [este comentario] (http://stackoverflow.com/questions/3818277/can-i-get-polymorphic-behavior-without-using-virtual-functions#comment65241419_3827715) –

-1

Creo que es posible con CRTP (si su 'Dispositivo' es compatible con 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(); 
} 
+0

OP estaba buscando claramente un (nunca justificado y probablemente equivocado) forma de implementar polimorfismo dinámico, en el sentido de que se puede acceder a objetos de diferentes tipos derivados a través de punteros polimórficos/referencias/contenedores con un comportamiento determinado en tiempo de ejecución. El CRTP no proporciona eso; solo permite la selección de diferentes comportamientos en tiempo de compilación, de ahí el mejor nombre _polífismo estático_. –

Cuestiones relacionadas