23 votos

Métodos virtuales o Punteros a Función

Al implementar el comportamiento polimórfico en C++ se puede utilizar un método virtual puro o uno puede utilizar punteros a función (o functors). Por ejemplo, una devolución de llamada asincrónica puede ser implementado por:

Enfoque 1

class Callback
{
public:
    Callback();
    ~Callback();
    void go();
protected:
    virtual void doGo() = 0;  
};

//Constructor and Destructor

void Callback::go()
{
   doGo();
}

Para utilizar el callback aquí, usted necesita para reemplazar el doGo() método para llamar a cualquier función que se desee

Enfoque 2

typedef void (CallbackFunction*)(void*)

class Callback
{
public:
    Callback(CallbackFunction* func, void* param);
    ~Callback();
    void go();
private:
   CallbackFunction* iFunc;
   void* iParam;
};

Callback::Callback(CallbackFunction* func, void* param) :
    iFunc(func),
    iParam(param)
{}

//Destructor

void go()
{
    (*iFunc)(iParam);
}

Para utilizar el método de devolución de llamada aquí usted tendrá que crear un puntero a función a ser llamada por el objeto de devolución de llamada.

Enfoque 3

[Esto se agregó a la pregunta por mí (Andreas); no fue escrito por el cartel original]

template <typename T>
class Callback
{
public:
    Callback() {}
    ~Callback() {}
    void go() {
    	T t; t();
    }
};

class CallbackTest
{
public:
    void operator()() { cout << "Test"; }
};

int main()
{
    Callback<CallbackTest> test;

    test.go();
}

¿Cuáles son las ventajas y desventajas de cada aplicación?

6voto

Andreas Bonini Puntos 15709

Enfoque 1

  • Fácil de leer y entender
  • Menos posibilidad de errores (iFunc no puede ser NULO, no estás usando un void *iParam, etc
  • Los programadores de C++ diré que esta es la manera "correcta" de hacerlo en C++

Enfoque 2

  • Un poco menos de escribir para hacer
  • MUY ligeramente más rápido (llamar a un método virtual tiene un costo, por lo general la misma de dos operaciones aritméticas simples.. Así que lo más probable es que no importa)
  • Que es como se haría en C

Enfoque 3

Probablemente la mejor manera de hacerlo cuando sea posible. Va a tener el mejor rendimiento, será el tipo de seguro, y es fácil de entender (es el método utilizado por el STL).

5voto

Adisak Puntos 3328

Enfoque 1 (Función Virtual)

  • "+" La "forma correcta de hacerlo en C++
  • "-" Una nueva clase debe ser creado por devolución de llamada
  • "-" En cuanto al rendimiento adicional de eliminar a través de VF-Tabla de comparación con el Puntero a Función. Dos referencias indirectas en comparación con Functor solución.

Enfoque 2 (Clase con la Función de Puntero)

  • "+" Se puede envolver un estilo C de la función de C++ de devolución de llamada Clase
  • "+" Función de devolución de llamada puede ser cambiado después de la devolución de llamada se crea un objeto
  • "-" Requiere una llamada indirecta. Puede ser más lento que el functor método para devoluciones de llamada que puede ser estática calculada en tiempo de compilación.

Enfoque 3 (Clase llamando T functor)

  • "+" Posiblemente la forma más rápida de hacerlo. Ninguna indirecta de la sobrecarga de la llamada y puede estar en línea por completo.
  • "-" Requiere un Functor de la clase a ser definido.
  • "-" Que requiere de devolución de llamada es estáticamente declarado en tiempo de compilación.


FWIW, los Punteros a Función, no son como los Functors. Functors (en C++) son clases que se utilizan para proporcionar una llamada a una función que normalmente es el operador().

Aquí es un ejemplo functor así como una función de la plantilla que utiliza un functor argumento:

class TFunctor
{
public:
    void operator()(const char *charstring)
    {
    	printf(charstring);
    }
};

template<class T> void CallFunctor(T& functor_arg,const char *charstring)
{
    functor_arg(charstring);
};

int main()
{
    TFunctor foo;
    CallFunctor(foo,"hello world\n");
}

Desde una perspectiva de rendimiento, funciones Virtuales y los Punteros a Función, tanto como resultado indirecto de una llamada de función (es decir, a través de un registro) a pesar de que las funciones virtuales requieren una carga adicional de la VFTABLE puntero antes de cargar el puntero de función. El uso de Functors (con un no-virtual llamada) como una devolución de llamada son los de más alto desempeño método a utilizar un parámetro de la plantilla de funciones debido a que pueden estar en línea y aunque no entre líneas, no generan una llamada indirecta.

4voto

Puppy Puntos 90818

El principal problema con el Enfoque de la 2 es que simplemente no escala. Considerar el equivalente de 100 funciones:

class MahClass {
    // 100 pointers of various types
public:
    MahClass() { // set all 100 pointers }
    MahClass(const MahClass& other) {
        // copy all 100 function pointers
    }
};

El tamaño de MahClass se ha disparado, y el tiempo para construir también ha aumentado significativamente. Las funciones virtuales, sin embargo, son O(1) aumento en el tamaño de la clase y el tiempo para construir, por no mencionar que usted, el usuario, debe escribir todas las devoluciones de llamada para todas las clases derivadas manualmente, ajuste el puntero para convertirse en un puntero a la deriva, y debe especificar los tipos de puntero de función y lo que es un lío. Por no hablar de la idea de que puede que se olvide de uno, o se establece en NULL o algo igual de estúpido, pero totalmente va a suceder porque está escrito de 30 clases de esta manera y violar SECO como el de una avispa parásita viola una oruga.

Enfoque 3 sólo es utilizable cuando el deseado de devolución de llamada es estáticamente conocible.

Esto deja Enfoque 1 como la única utilizable enfoque dinámico de la invocación del método es necesario.

2voto

NVRAM Puntos 2555

No está claro a partir de su ejemplo, si estás creando una clase de utilidad o no. Es usted de devolución de llamada clase intención de implementar un cierre o un más sustancial objeto que sencillamente no la carne?

La primera forma:

  • Es más fácil de leer y entender,
  • Es mucho más fácil de extender: trate de añadir métodos de pausar, reanudar y detener.
  • Es mejor en el manejo de encapsulación (suponiendo doGo se define en la clase).
  • Es probablemente una mejor abstracción, así será más fácil de mantener.

La segunda forma:

  • Puede ser utilizado con diferentes métodos para el doGo, así que es más que sólo polimórficos.
  • Podría permitir (con métodos adicionales) cambiar el doGo método en tiempo de ejecución, permitiendo a las instancias del objeto a mutar su funcionalidad después de la creación.

En última instancia, la OMI, la primera forma es la mejor para todos los casos normales. La segunda tiene algunas interesantes funciones, sin embargo -- pero no necesitará a menudo.

1voto

R Samuel Klatchko Puntos 44549

Una gran ventaja del primer método es que tiene más seguridad de tipos. El segundo método utiliza un void * para iParam por lo que el compilador no va a ser capaz de diagnosticar los problemas del tipo.

Un menor de ventaja del segundo método es que sería menos trabajo para integrar con C. Pero si estás código base es de C++, esta ventaja es discutible.

Iteramos.com

Iteramos es una comunidad de desarrolladores que busca expandir el conocimiento de la programación mas allá del inglés.
Tenemos una gran cantidad de contenido, y también puedes hacer tus propias preguntas o resolver las de los demás.

Powered by:

X