valores todas tipos retornan que parametros las funciones funcion ejemplos ejemplo con c++ boost event-handling c++11 boost-any

todas - void c++ ejemplo



Sistema de eventos con uso de la función<void(boost:: any)> ¿buena idea? (2)

He hecho un sistema de módulos, algo como esto:

//setting event module->set_event("started", [](boost::any ev) { cout << "The module have been successfully started" << endl; }); //somewhere else module->start(); //impl void Module::start() { //run once protection here this->trigger_event("start");//pre start this->_impl->start(); //on error, throw exception this->trigger_event("started"); //post start } void Module::trigger_event(string str,boost::any ev = boost::any() ) { //mutex //invokes every handler which have been invoked set_event }

Muy simple y fácil de entender. El primer inconveniente es que no hay "remove_event", pero para permitirlo (si lo necesito) puedo simplemente devolver una función std :: (en el método set_event) que en la invocación elimina el controlador, por lo que si el usuario desea eliminar el evento, solo invoque el manejador devuelto:

function<void ()> h = module->set_event(...); //... somewhere later h() //removes the handler safely

¿Pero soy curioso si este es un buen diseño? ¿O de repente descubriré desventajas muy grandes usando el enfoque de la mina y tendré que volver a escribir la mayoría del código? Puedo decir que el método "trigger_event" también se usará como un sistema de mensajes entre módulos, al igual que el método jQuery de la biblioteca JavaScript: "bind" permite. Un ejemplo:

module->set_event("message", [](boost::any message) { //deal with the message here }); //send message module->trigger_event("message", MyMessageTypeHere);

¿Es este un diseño aceptado en la mayoría de los casos? Quiero un enfoque muy flexible, por lo tanto, mi idea es utilizar boost :: any, pero ¿es esta una buena idea (escalable, rendimiento, flexibilidad) en general?


Es problemático por dos razones.

En primer lugar, limite sus eventos a los controladores que reciben exactamente un parámetro, mientras que a juzgar por su uso, puede tener varios sabores de eventos. Entonces, esto es limitante (sí, podrías lanzar una std::tuple en cualquier tipo, para codificar varios parámetros, pero, ¿realmente quieres esto?).

Más importante aún, el debilitamiento de la seguridad de tipo implica, bueno, una seguridad debilitada (tipo). Si tu controlador de eventos solo puede trabajar con una cabra, no le des un cordero. Lo que quiere, parece ser posible con plantillas (en C ++ 11, al menos), ya que puede hacer que trigger_event una función de tipo, que recibe una cantidad arbitraria de argumentos. A continuación, intenta invocar el controlador con estos argumentos, lo que dará un error de compilación, si no proporcionó el número y tipo de argumentos correctos.

Aquí, en su despachador, puede ocultar la resolución de tipo, lo que es un poco complicado, porque desea almacenar eventos de tipo arbitrario. Este podría ser el lugar para usar any , pero mantenerse dentro de la implementación / lógica de su planificador, sin escaparse a sus usuarios.

Editar: Para lograr esta resolución, mi enfoque sería tener algún tipo de Event abstracto interno (del cual puedes almacenar punteros) y algunas template <typename Hnd> classs ConcreteEvent : public Event que te permite mantener a tus clientes manejadores de eventos como ConcreteEvent<EventType> (suponiendo que EventType es el parámetro de plantilla para su método set_event ). Con rtti puede verificar luego si al recuperarlo (solo lo recuperará como Event* ) coincide con el tipo al que desea llamar.


Hay varias cosas que cambiaría.

Primero, ignoremos el retorno por un momento y enfóquese en ese boost::any combinación con esa std::string . Esa es realmente una mala idea para un sistema de eventos, ya que permite un posible desajuste del tipo de tiempo de ejecución. Simplemente no quiere un diseño que haga posible cometer esos errores (porque entonces esos errores se cometerán, eventualmente, y habrá errores que deben corregirse, desperdiciando tiempo en el futuro).

Típicamente, lo que realmente quieres es:

void Module::trigger_event(shared_ptr<Event> const& event) { // Event is a base interface // pull off event->ID() to get identifier and lookup // dispatch to something that has the signature // void (shared_ptr<SpecificEvent>) }

Para hacer algo como el despachador, generalmente tienes

map<IDType, shared_ptr<Dispatcher>> dispatchDictionary_;

con

template <typename SpecificEvent> class SpecificDispatcher : public Dispatcher { public: SpecificDispatcher(function<void (shared_ptr<SpecificEvent>)> handler) handler_(handler) {} virtual void dispatch(shared_ptr<Event> event) { auto specificEvent(static_ptr_cast<SpecificEvent>(event)); handler_(specificEvent); } };

(con la clase de interfaz definida de forma obvia y métodos de registro / anulación de registro para asociar los ID de tipo de evento en el mapa). El objetivo es asociar el ID con el EventType dentro de la clase, para garantizar que sea siempre la misma asociación, y enviar de forma tal que los manipuladores no necesiten reinterpretar los datos ellos mismos (error propenso). Haz el trabajo que se puede automatizar dentro de tu biblioteca, para que no se haga incorrectamente afuera.

De acuerdo, ahora ¿qué devuelves con tu método? Devuelve un objeto, no una función. Si no necesitan llamarlo explícitamente, ha guardado otra posible fuente de error. Es como todo RAII: no desea que las personas tengan que recordar llamar a eliminar, o desbloquear, o ... De forma similar, con "unregister_event" (o como se llame).

Hay cuatro vidas que las personas deben preocuparse en un programa: expresión, función-alcance, estado y programa. Estos corresponden a los cuatro tipos de cosas que pueden almacenar el objeto en el que regresas: anónimo (deja que caiga en el suelo; puede usarse dentro de la expresión pero nunca se le asigna a un objeto nombrado), un objeto de alcance automático, un objeto es un miembro de una clase de estado (en el patrón de estado) que existe entre dos eventos de transición asincrónicos, o un objeto de alcance global sentado hasta la salida.

Toda la administración de por vida debe ser administrada con RAII. Es una especie de punto de los destructores y es cómo se supone que la limpieza de recursos se debe manejar frente a las excepciones.

EDITAR: Dejé un montón de piezas sin especificar, señalando que conectarías los puntos "de la manera más obvia". Pero pensé que iba a completar más piezas (ya que tengo una compilación del sistema operativo e instalación en marcha y mis errores están ahora en una última, que está esperando la instalación ...)

El punto era que alguien debería ser capaz de simplemente escribir

callbackLifetimeObject = module->set_event<StartEvent>([](shared_ptr<StartEvent> event){ cout << "Module started: " << event->someInfo() << endl; });

entonces el set_event necesita tener el tipo de firma para tomar esto, y debe insertar el despachador apropiado en el diccionario. Hay varias maneras de obtener la identificación del tipo aquí. La forma "obvia" es simplemente crear un miembro temporal y llamarlo "ID", pero eso tiene una sobrecarga de creación de objetos. Otra forma es convertirlo en "estático virtual", y obtener el estático (que es todo lo que hace el método virtual). Cada vez que tengo estática virtual, tiendo a convertirlos en rasgos, lo mismo, pero una encapsulación ligeramente mejor y la capacidad de modificar también las "clases ya cerradas". Luego, el método virtual solo llama a la clase de rasgos para devolver lo mismo.

Asi que...

template <typename EventType> struct EventTrait { // typedef your IDType from whatever - looks like you want std::string static IDType eventID() { /* default impl */ } }; template <> struct EventTrait<StartEvent> { // same as above static IDType eventID() { return "Start"; } };

entonces tú puedes

template <typename EventType> EventRegistration set_event(function<void (shared_ptr<EventType>)> handler) { auto id(EventTrait<EventType>::eventID()); dispatchDictionary_.insert(make_pair(id, make_shared<SpecificDispatcher<EventType>>(handler))); return EventRegistration(bind(& Module::unset_event, this, id)); }

Eso debería ilustrar un poco mejor cómo encajar las piezas y algunas de las opciones que tiene. Algo de esto ilustra el código repetitivo que puede ser recodificado con cada evento. Ese es el tipo de cosas que probablemente quiera automatizar también. Existen muchas técnicas avanzadas para metaprogramar dicha generación, desde trabajar con otro paso de compilación que requiere una especificación para trabajar dentro del sistema de metaprogramación de c ++. Una vez más, al igual que con los puntos anteriores, cuanto más pueda automatizar, menos errores tendrá.