2012-04-02 13 views
6

Estoy intentando implementar un bus de eventos seguro. Estoy atascado con la función EventBus::subscribe porque no acepta mi controlador de evento concreto. En una versión anterior tenía mi AbstractEventHandler implementado como una clase abstracta solamente, sin que fuera una plantilla. No tuve ningún problema con esa implementación. Es por eso que asumo que el problema real es con la plantilla abstracta.La función C++ no acepta la implementación concreta

El siguiente código es una versión simplificada de mi implementación. El primer bloque consiste en el "esqueleto" del bus de eventos y sus clases requeridas, mientras que el segundo bloque muestra una implementación real del evento, el controlador de eventos y el principal.

El enum contiene todos los diferentes eventos disponibles. El evento abstracto es la base de la cual se derivan todos los eventos concretos. El controlador de eventos es una plantilla abstracta con un evento como clase de plantilla para garantizar la seguridad de tipo. El bus de eventos es responsable de distribuir todos los eventos publicados a sus respectivos manejadores.

enum EVENT_TYPE 
{ 
    ON_EVENT_1, 
    ON_EVENT_2 
}; 

class AbstractEvent 
{ 
public: 
    AbstractEvent() {}; 
    virtual ~AbstractEvent() {}; 

    virtual EVENT_TYPE type() = 0; 
}; 

template<class T> 
class AbstractEventHandler 
{ 
public: 
    AbstractEventHandler() {}; 
    virtual ~AbstractEventHandler() {}; 

    virtual void on_event(T *event) = 0; 
}; 

class EventBus 
{ 
public: 
    EventBus() {}; 
    virtual ~EventBus() {}; 

    void subscribe(EVENT_TYPE type, 
        AbstractEventHandler<AbstractEvent> *eventHandler) { 
     // Add handler to vector for further use 
    } 

    void publish(AbstractEvent *event) { 
     // send event to each handler in respective vector 
    } 
}; 

continuación son mis concreta controlador de eventos y el evento y el principal()

class ConcreteEvent : public AbstractEvent 
{ 
public: 
    ConcreteEvent() {}; 
    virtual ~ConcreteEvent() {}; 

    EVENT_TYPE type() { 
     return ON_EVENT_1; 
    }; 
}; 

class ConcreteEventHandler : public AbstractEventHandler<ConcreteEvent> 
{ 
public: 
    ConcreteEventHandler() {} 
    virtual ~ConcreteEventHandler() {}; 

    void on_event(ConcreteEvent *event) { 
     // Do something 
    }; 
}; 

int main() 
{ 
    EventBus *eventBus = new EventBus(); 

    ConcreteEventHandler handler = ConcreteEventHandler(); 

    // This failes! 
    eventBus->subscribe(ON_EVENT_1, &handler); 
} 

Los rendimientos del compilador con decir un error que no hay una función coincidente para la llamada a

EventBus::subscribe(EVENT_TYPE, ConcreteEventHandler*) 

y que los únicos candidatos son

void EventBus::subscribe(EVENT_TYPE, AbstractEventHandler<AbstractEvent>*) 

¿Cómo puedo implementar mi método EventBus :: subscribe para aceptar implementaciones concretas de mi clase abstracta?

Actualización: Solución

me han cambiado la descripción del método de EventBus::subscribe a la siguiente y que ahora trabaja muy bien:

template<typename T> 
void subscribe(EVENT_TYPE type, AbstractEventHandler<T> *eventHandler) { 

} 

Gracias, Rohan, por sus consejos! Me ayudaron a encontrar esta solución.

+0

+1 para una pregunta bien hecha. –

Respuesta

5

La razón se debe a que, ConcreteEventHandler es una subclase de AbstractEventHandler<ConcreteEvent> y no AbstractEventHandler<AbstractEvent>.

Esto puede parecer sorprendente, pero AbstractEventHandler<ConcreteEvent> no puede ser una subclase de AbstractEventHandler<AbstractEvent> a pesar de que ConcreteEvent es una subclase de AbstractEvent.

La razón es porque, con plantillas, crear plantillas como desee no garantiza la seguridad del tipo. Veamos un ejemplo. Repasemos el paradigma estándar de una clase base Animal y las subclases Cat y Dog.Digamos que tenemos una lista de los Animales:

std::list<Animals>* animals; 

y una lista de los gatos:

std::list<Cat> cats; 

A continuación, no es un reparto válida:

animals = &cats; 

La razón es, porque , si voy a hacer esto,

animals->add(new Dog("Ben")); 

I wo uld realmente agrega un Dog a una lista de Cat s. cats.last() aquí realmente devolvería un Dog. Entonces, en este caso, básicamente está agregando un Dog a una lista de Cat s. He visto suficientes episodios de Looney Tunes sepan que esto no es una buena idea:

cats.last().meow(); 

Lo anterior no es definitivamente cierto, como todos sabemos que un Dog sólo puede bowbow().

EDITAR

Para responder a su pregunta, esto es lo que te sugiero que hagas; Deje ConcreteEventHandler heredar de AbstractEventHandler<AbstractEvent>, y dentro del código, donde sea que use ConcreteEvent, use dynamic_case para convertir AbstractEvent en ConcreteEvent. Esto utilizará introspección en tiempo de ejecución, lo que podría afectar un poco el rendimiento (también he visto a muchas personas opuestas a usar un molde dinámico), pero podrá realizar una conversión ascendente válida del tipo de datos.

+0

De acuerdo, cierto, pero no responde a la pregunta: ¿cómo puedo implementar mi método EventBus :: subscribe para aceptar implementaciones concretas de mi clase abstracta? –

+0

¡Gracias por su respuesta rápida! Ahora entiendo el problema mejor. El problema es que necesito AbstractEventHandler en oder para forzar a los manejadores concretos a implementar el método on_event() correcto. – Fabian

+1

¿Ambos son como la misma persona? –

-2

Su clase AbstractEventHandler<T> debe heredar AbstractEvent. Esta fue probablemente tu intención, olvidaste escribirla.

template<class T> 
class AbstractEventHandler 
    :public AbstractEvent 
{ 
public: 
    AbstractEventHandler() {}; 
    virtual ~AbstractEventHandler() {}; 

    virtual void on_event(T *event) = 0; 
} 
+1

-1 no solo por estar equivocado, sino por no haber intentado siquiera compilar el código. –

+0

semánticamente, un eventhandler nunca debe heredar un evento. No es un evento, es lo que está manejando ese evento. – stefaanv

1

Rohan ya respondió por qué el código no se compila, sin embargo, me gustaría sugerir otro enfoque.

Puede implementarlo de forma que Eventhandler se suscriba directamente al EventGenerator. De esta forma, existe un vínculo directo entre generar y manejar el evento.
El evento debe mantener una referencia a su generador para permitirle acceder a los controladores suscritos y eventbus llama a un método en el evento para que lo maneje solo.

De esta manera, el eventbus no tiene conocimiento de eventhandlers y ni siquiera necesita un enum de tipo de evento.
Sin embargo, necesita diferentes generadores de eventos que deben ser accesibles mediante diferentes manejadores de eventos en lugar de un eventbus. Cada controlador de eventos solo puede manejar un evento, por lo que si se necesitan más eventos, los manejadores de eventos se deben agregar (por delegación o herencia).

+0

¡Gracias por su sugerencia!No quiero seguir esta ruta por dos razones: 1/El bus de eventos es parte de una API y no quiero exponer las clases que generan los eventos. 2/A medida que el proyecto crece, me temo que terminaré en una red incontrolable de interconexiones entre manipuladores y generadores de eventos. – Fabian

+0

Bastante bien, es su proyecto. Alrededor de 2 /, el riesgo se minimiza con un enfoque estratificado, donde los módulos superiores conocen los módulos inferiores y pueden suscribirse, y donde los eventos solo se envían desde módulos inferiores a superiores. – stefaanv

Cuestiones relacionadas