2011-01-19 31 views
30

En resumen: Desde un puntero de clase base C++ que apunta a una instancia de una clase derivada, ¿cómo se puede determinar en tiempo de ejecución si la función virtual no pura (con una implementación en la clase base) se ha vuelto a implementar en la clase derivada?Formas de detectar si una función virtual C++ ha sido redefinida en una clase derivada

Contexto: Estoy escribiendo una biblioteca en C++ para resolver ciertas clases de ecuaciones matemáticas. La biblioteca proporciona una clase Equation con varias funciones virtuales, que los usuarios de la biblioteca utilizan como una clase base para la ecuación particular que desean resolver. La biblioteca también proporciona una clase Solver, que toma un Equation * como un parámetro de constructor. Entonces, el usuario escribe el código a lo largo de las líneas de:

class MyEquation : public Equation 
{ ... } // equation definition here 

int main() 
{ 
    MyEquation myEqn; 
    Solver solver(&myEqn); 
    solver.Solve(); 
} 

Si ciertas combinaciones de las funciones virtuales en Equation no se redefine en la clase ecuación derivada, ciertas partes computacionalmente caras del algoritmo de ejecutar por el objeto Solver pueden omitirse . Por lo tanto, me gustaría saber, en el constructor de Solver, qué funciones se han redefinido, y cuáles ejecutarán la implementación predeterminada en Equation.

  • me gustaría hacer este transparente para los usuarios de la biblioteca, así que no estoy buscando una solución que, por ejemplo, el usuario establece algunas banderas en el constructor de su especificando ecuación derivada qué funciones se han redefinido .

  • Una posible solución es para las implementaciones predeterminadas de las funciones virtuales en Equation para establecer un indicador privado en la clase Equation; El constructor de la clase Solver puede borrar este indicador, ejecutar la función virtual y verificar el valor del indicador para ver si se ha llamado a la implementación en Equation. Me gustaría evitar esto, porque simplemente establecer el indicador cada vez que se ejecuta la función virtual ralentiza mucho el algoritmo (la ejecución de estas funciones virtuales contribuye significativamente al tiempo de ejecución del programa, y ​​las implementaciones predeterminadas simplemente regresan una constante).

+2

¿Estás seguro de que establecer una marca hace alguna diferencia? Me imagino que es completamente insignificante en comparación con la sobrecarga de invocar una llamada de función virtual. –

+1

Eso parece ser altamente dependiente de la arquitectura. Para este programa, en el servidor Xeon de 64 bits que es la máquina de destino, la sobrecarga para llamadas a funciones virtuales parece ser prácticamente nula. En la computadora de mi casa (Athlon de 32 bits), sin embargo, como sospechabas, la sobrecarga de llamada a la función fue mucho mayor que el tiempo para configurar una bandera. –

+3

¿No tiene la necesidad de determinar si una virtual tiene una anulación que le parece totalmente ortogonal al polimorfismo en primer lugar? Ciertamente me hace. En otras palabras, si necesita hacer esto, su diseño está roto. –

Respuesta

19

No puede verificar la anulación de la función virtual de forma portátil.

Debe llevar el conocimiento al Solver, preferiblemente a través del tipo en oposición al indicador de tiempo de ejecución.

Una manera (basada en el tipo) es verificar la presencia o ausencia de una interfaz, a través de dynamic_cast.

Una forma probablemente mejor es proporcionar sobrecargas de la función de resolver, en su caso el Solver constructor.

Probablemente se podrían dar mejores consejos si proporcionó una descripción más concreta del problema.Recuerda la situación típica en la que alguien (1) necesita resolver algún problema P, (2) visualiza el enfoque técnico X como una solución a P, (3) descubre que X no lo corta y (4) pregunta cómo hacer que X funcione para una descripción vaga de P, o incluso de algún problema no relacionado P. Los detalles del problema original P a menudo sugieren una solución mucho mejor que X y los problemas de hacer que X funcione, irrelevante para resolver P.

+6

El último párrafo hizo mi día. +1 –

+1

Gracias. Estoy de acuerdo en general con su último párrafo; me parece que en algunos casos la respuesta a X (que dice "No, porque ...") es más útil para SO (y posiblemente más útil para la pregunta). -asker, a largo plazo) que una respuesta a P sería. –

2

No se puede hacer de la forma en que lo intentas. No hay forma de que las funciones de la clase base sepan si una función virtual que implementan ha sido anulada o no, sin que se les haya dicho explícitamente.

Parecería que necesita volver al tablero de dibujo. No estoy seguro de cómo resolvería su problema de diseño, pero lo que está intentando ahora, simplemente sé que no funcionará.

4

Aunque esto probablemente sea de alguna manera posible, aconsejaría no hacerlo. Usted es:

  • Violando los principios de OOP. Si se le proporciona una clase/interfaz general, no debe verificar los detalles de implementación. Esto aumenta la dependencia entre las clases y es exactamente lo opuesto a lo que se diseñó para OOP.
  • Código de duplicación. Considerando que usa las constantes que la clase Equation devolvería de lo contrario.
  • Código de ofuscación. Muchas condiciones con comprobaciones de tipo harán que su programa se vea feo.
  • Probablemente haciendo una optimización prematura. Casi no hay diferencia de velocidad entre la ejecución de una llamada de función virtual y una condición. ¿Ha ejecutado su generador de perfiles para comprobar si las funciones virtuales son el cuello de botella? Casi nunca es el caso en una aplicación/biblioteca bien diseñada.
+4

- [¿Ha ejecutado su generador de perfiles para comprobar si se trata de las funciones virtuales que son el cuello de botella] - Sí. –

2

¿Y si no usó los métodos virtuales?

En este punto, se me ocurre una solución basada en plantillas. Es posible (aunque no tan fácil) usar plantillas para detectar si una clase determinada tiene un método con una determinada firma, o no. Una vez identificado, puede cambiar, en tiempo de compilación, entre el esquema de peso ligero y el esquema pesado.

Tenga en cuenta que no sugiero que todo el código se modele, el código de plantilla solo cubrirá la parte Template (Patrón de diseño) de la solución, y el trabajo pesado se puede realizar con funciones regulares para cortar dependencias.

Además, esto puede ser completamente transparente para los clientes, siempre que no altere la firma de los métodos.

2

No sé cómo hacer tal detección, pero, ¿consideraste usar polimorfismo estático? Las optimizaciones se pueden realizar en tiempo de compilación si cada método virtual de Ecuación se reemplaza con una plantilla de "política" con valor predeterminado.

//default implementation of a virtual method turned into a functor 
//which optionally takes a reference to equation 
class DefaultFunctor1 
{ 
public: 
    //... 
    double operator()(double x) { return log(x); } 
}; 
template<class F1 = DefaultFunctor1> 
class Equation 
{ 
public: 
    typedef F1 Functor1; 
    //... 
private: 
    F1 f1; 
}; 
class Solver 
{ 
public: 
    //.... 
    template<class Equation> 
    void solve(Equation& eq) 
    { 
     Loki::Int2Type< 
         Loki::IsSameType<Equation::Functor1, DefaultFunctor1>::value 
         > selector; 
     //choose at compile time appropriate implementation among overloads of doSolve 
     doSolve(selector); 
    } 
private: 
    //.... overloads of doSolve here 
}; 
int main() 
{ 
    Equation<> eq; 
    Solver solver; 
    solver.solve(eq); //calls optimized version 
    Equation<MyFunctor> my_eq; 
    solver.solve(my_eq); //calls generic version 
    return 0; 
} 
1

Quizás esto ayude. Por sí solo no responde a su pregunta original, pero puede aumentar su clase base (aquí, Foo) para usar una determinada interfaz si se suministra o utilizar un método predeterminado de lo contrario.

#include <iostream> 

struct X { 
    virtual void x() = 0; 
}; 

struct Y { 
    virtual void y() = 0; 
}; 


struct Foo { 
    virtual ~ Foo() {} 

    bool does_x() { 
     return NULL != dynamic_cast <X*> (this); 
    } 

    bool does_y() { 
     return NULL != dynamic_cast <Y*> (this); 
    } 

    void do_x() { 
     dynamic_cast <X&> (*this) .x(); 
    } 

    void do_y() { 
     dynamic_cast <Y&> (*this) .y(); 
    } 
}; 

struct foo_x : public Foo, public X { 
    void x() { 
     std :: cout << __PRETTY_FUNCTION__ << std :: endl; 
    } 
}; 


struct foo_y : public Foo, public Y { 
    void y() { 
     std :: cout << __PRETTY_FUNCTION__ << std :: endl; 
    } 
}; 

struct foo_xy : public Foo, public X, public Y { 
    void x() { 
     std :: cout << __PRETTY_FUNCTION__ << std :: endl; 
    } 

    void y() { 
     std :: cout << __PRETTY_FUNCTION__ << std :: endl; 
    } 
}; 

void test (Foo & f) 
{ 
    std :: cout << &f << " " 
     << "{" 
     << "X:" << f .does_x() << ", " 
     << "Y:" << f .does_y() 
     << "}\n"; 

    if (f .does_x()) 
     f .do_x(); 

    if (f .does_y()) 
     f .do_y(); 
} 

int main() 
{ 
    foo_x x; 
    foo_y y; 
    foo_xy xy; 
    test (x); 
    test (y); 
    test (xy); 
} 
0

¿Qué hay de conseguir puntero a la función de la clase base en el primer uso y compararlo con uno real

class Base { virtual int foo(); } 
    class Derived : public Base { virtual int foo(); } 

    bool Xxx::checkOverride() 
    { 
     int (Base::*fpA)(); 
     int (Base::*fpb)(); 

     fpA = &Base::foo; 
     fpB = &Derived::foo; 

     return (fpA != fpB); 
    } 
9

Para referencia futura, resulta que GCC proporciona esta extensión: http://gcc.gnu.org/onlinedocs/gcc/Bound-member-functions.html que permite comprobar si un método fue anulado con

(void*)(obj.*(&Interface::method)) != (void*)(&Interface::method) 

CIC apoya esta extensión oficialmente, documentos de clang no lo mencionan, pero funciona el código e incluso compila sin la advertencia.

MSVC no es compatible con esto, sin embargo, eso es todo.

Además, parece que no funciona con los métodos definidos en la cabecera (es decir, en línea) en una biblioteca independiente si se vincula a una versión diferente de la biblioteca en la que la aplicación ha cambiado. Si interpreto el estándar correctamente, este es un comportamiento indefinido (cambiando la implementación), pero si la implementación se mantiene igual, entonces la dirección también debe ser única. Entonces no hagas eso con los métodos en línea.

+0

¡Gracias, esto es genial! Estaba teniendo un problema similar, donde proporciono una implementación predeterminada vacía de un método, y puedo omitir cierto código si sé que el método no se ha anulado. Si esta optimización requiere una extensión GCC, eso está bien para mí. Por cierto, el elenco para anular * no funcionó para mí, GCC solo aceptó un elenco para el tipo de función correcto (con la interfaz * como el primer parámetro, como se describe en el documento al que se ha vinculado). –

+1

¿Podría alguien explicar cómo es esto más correcto que simplemente comparar los punteros de función? es decir '& obj.method! = & Interface :: method', que no requiere ninguna extensión? – galinette

6

Después de encontrar solo resultados que vinculen con la extensión PMF de gcc, pensé que debía haber una forma adecuada de hacerlo.

he encontrado una solución sin ningún hacks y es al menos probado para trabajar en gcc & llvm:

#include <iostream> 
#include <typeinfo> 

struct A { virtual void Foo() {} }; 
struct B : public A { void Foo() {} }; 
struct C : public A { }; 

int main() { 
    std::cout << int(typeid(&A::Foo) == typeid(&A::Foo)) << std::endl; 
    std::cout << int(typeid(&A::Foo) == typeid(&B::Foo)) << std::endl; 
    std::cout << int(typeid(&A::Foo) == typeid(&C::Foo)) << std::endl; 
    return 0; 
} 

http://ideone.com/xcQOT6

PS: en realidad lo uso en un sistema de CEventClient. Entonces deriva su clase de CEventClient y si anula un método de evento automáticamente 'vinculará' el evento.

+1

En lugar de comparar 'typeid's, también puede usar' std :: is_same :: value'. Desafortunadamente, sigue siendo una verificación en tiempo de compilación, en lugar de la verificación en tiempo de ejecución que el OP está solicitando. – bcmpinc

+0

Parece que esto solo funciona para las funciones de miembro público. – james

Cuestiones relacionadas