C ++ 11 y manejo de eventos

Creo que el procesamiento de eventos como una forma de interacción entre objetos en OOP es conocido por casi todos los que alguna vez han tocado OOP al menos una vez. Al menos, este enfoque es muy conveniente en un rango muy amplio, en mi opinión, de tareas. En muchos lenguajes de programación, el motor de manejo de eventos está incorporado; sin embargo, en C ++ no existe tal mecanismo. Veamos qué puedes hacer al respecto.

Breve introducción


Un evento es algo que puede sucederle a algún objeto bajo ciertas condiciones (por ejemplo, con un botón cuando hace clic con el mouse). Otras entidades pueden necesitar ser conscientes de esto; luego se suscriben al evento . En este caso, cuando se produce un evento, se llama al controlador de un objeto de terceros suscrito al evento; Por lo tanto, tiene la oportunidad de ejecutar algún código, es decir responder a un evento. Del mismo modo, un objeto puede darse de baja de un evento si ya no quiere responder a él. Como resultado, tenemos muchos objetos que pueden conectarse entre sí utilizando los eventos de uno de ellos y la reacción a estos eventos de otros.

Algo así, aunque todos lo saben.

Implementación más simple


Parecería fácil implementar tal comportamiento. Y podría verse así:

template<class ...TParams> class AbstractEventHandler { public: virtual void call( TParams... params ) = 0; protected: AbstractEventHandler() {} }; 

 template<class ...TParams> class TEvent { using TEventHandler = AbstractEventHandler<TParams...>; public: TEvent() : m_handlers() { } ~TEvent() { for( TEventHandler* oneHandler : m_handlers ) delete oneHandler; m_handlers.clear(); } void operator()( TParams... params ) { for( TEventHandler* oneHandler : m_handlers ) oneHandler->call( params... ); } void operator+=( TEventHandler& eventHandler ) { m_handlers.push_back( &eventHandler ); } private: std::list<TEventHandler*> m_handlers; }; 

 template<class TObject, class ...TParams> class MethodEventHandler : public AbstractEventHandler<TParams...> { using TMethod = void( TObject::* )( TParams... ); public: MethodEventHandler( TObject& object, TMethod method ) : AbstractEventHandler<TParams...>(), m_object( object ), m_method( method ) { assert( m_method != nullptr ); } virtual void call( TParams... params ) override final { ( m_object.*m_method )( params... ); } private: TObject& m_object; TMethod m_method; }; template<class TObject, class ...TParams> AbstractEventHandler<TParams...>& createMethodEventHandler( TObject& object, void( TObject::*method )( TParams... ) ) { return *new MethodEventHandler<TObject, TParams...>( object, method ); } #define METHOD_HANDLER( Object, Method ) createMethodEventHandler( Object, &Method ) #define MY_METHOD_HANDLER( Method ) METHOD_HANDLER( *this, Method ) 

La aplicación de este caso debe tener la forma:

 class TestWindow { . . . public: TEvent<const std::string&, unsigned int> onButtonClick; . . . }; class ClickEventHandler { . . . public: void testWindowButtonClick( const std::string&, unsigned int ) { ... } . . . }; int main( int argc, char *argv[] ) { . . . TestWindow testWindow; ClickEventHandler clickEventHandler; testWindow.onButtonClick += METHOD_HANDLER( clickEventHandler, ClickEventHandler::testWindowButtonClick ); . . . } 

Naturalmente, un método de manejador (-function-member de una clase) no será el único tipo de manejadores, sino más adelante.

Todo parece ser conveniente, compacto y excelente. Pero si bien hay una serie de deficiencias.

Comparación de manejadores


Para implementar la cancelación de la suscripción a un evento, es necesario agregar la posibilidad de comparación al controlador (por == y ! == ). Dichos manejadores que invocan el mismo método (una función miembro de una clase) del mismo objeto (es decir, la misma instancia de la misma clase) se considerarán iguales.

 template<class ...TParams> class AbstractEventHandler { . . . using MyType = AbstractEventHandler<TParams...>; public: bool operator==( const MyType& other ) const { return isEquals( other ); } bool operator!=( const MyType& other ) const { return !( *this == other ); } protected: virtual bool isEquals( const MyType& other ) const = 0; . . . }; 

 template<class TMethodHolder, class ...TParams> class MethodEventHandler : public AbstractEventHandler<TParams...> { . . . using TMethod = void( TObject::* )( TParams... ); protected: virtual bool isEquals( const AbstractEventHandler<TParams...>& other ) const override { const MyType* _other = dynamic_cast<const MyType*>( &other ); return ( _other != nullptr && &m_object == &_other.m_object && m_method == _other.m_method ); } private: TObject& m_object; TMethod m_method; . . . }; 

Luego podremos eliminar los controladores de la suscripción al evento. En este caso, es necesario prohibir agregar los mismos manejadores (iguales).

 template<class ...TParams> class TEvent { . . . using TEventHandler = AbstractEventHandler<TParams...>; using TEventHandlerIt = typename std::list<TEventHandler*>::const_iterator; public: bool operator+=( TEventHandler& eventHandler ) { if( findEventHandler( eventHandler ) == m_handlers.end() ) { m_handlers.push_back( &eventHandler ); return true; } return false; } bool operator-=( TEventHandler& eventHandler ) { auto it = findEventHandler( eventHandler ); if( it != m_handlers.end() ) { TEventHandler* removedEventHandler = *it; m_handlers.erase( it ); delete removedEventHandler; return true; } return false; } private: inline TEventHandlerIt findEventHandler( TEventHandler& eventHandler ) const { return std::find_if( m_handlers.cbegin(), m_handlers.cend(), [ &eventHandler ]( const TEventHandler* oneHandler ) { return ( *oneHandler == eventHandler ); } ); } std::list<TEventHandler*> m_handlers; . . . }; 

Aquí, las funciones de agregar / quitar del controlador devuelven verdadero si tiene éxito y falso si no se realizó la acción correspondiente (agregar o quitar).

Sí, el caso de uso con comparación implica la creación de controladores temporales agregados en ninguna parte que no se eliminen en ningún lado. Pero más sobre eso más tarde.

¿Se puede usar esto? Aún no está completamente implementado.

Eliminar un controlador dentro de un controlador


Por lo tanto, nos encontramos inmediatamente con un bloqueo durante la ejecución del código, donde el controlador se da de baja del evento (creo que no es el caso de uso más raro cuando el controlador se corta automáticamente bajo ninguna circunstancia):

 class TestWindow { . . . public: TEvent<const std::string&, unsigned int> onButtonClick; static TestWindow& instance(); . . . }; class ClickEventHandler { . . . public: void testWindowButtonClick( const std::string&, unsigned int ) { TestWindow::instance().onButtonClick -= MY_METHOD_HANDLER( ClickEventHandler::testWindowButtonClick ); } . . . }; int main( int argc, char *argv[] ) { . . . ClickEventHandler clickEventHandler; TestWindow::instance().onButtonClick += METHOD_HANDLER( clickEventHandler, ClickEventHandler::testWindowButtonClick ); . . . } 

El problema surge por una razón muy simple:

  • el evento se activa y comienza a iterar (usando iteradores) controladores, llamándolos;
  • el siguiente controlador dentro de sí mismo hace que se elimine;
  • el evento elimina el controlador dado, lo que invalida el iterador correspondiente;
  • después de completar este controlador, el evento vuelve a enumerar los otros, sin embargo, el iterador actual (correspondiente al controlador remoto) ya no es válido;
  • el evento está intentando acceder al iterador inválido, causando una caída.

Por lo tanto, es necesario verificar los casos en que se puede cambiar la lista de manejadores, lo que llevaría a la invalidación de los iteradores; y luego implementar protección de lectura para dichos iteradores.

La ventaja de std :: list 'en esta aplicación es el hecho de que al eliminarlo solo se invalida un iterador: en el elemento eliminado (que afecta, por ejemplo, lo siguiente); y agregar un elemento no conduce a la invalidación de ningún iterador en absoluto. Por lo tanto, necesitamos controlar el único caso: eliminar un elemento cuyo iterador es actual en la enumeración actual de elementos. En este caso, puede, por ejemplo, no eliminar un elemento, sino simplemente marcar que el elemento actual se eliminará y dejar que se haga dentro de la enumeración de elementos.

Sería posible implementar de inmediato la implementación de esto, pero propongo resolver este problema junto con lo siguiente.

Hilo de seguridad


Potencialmente, las llamadas a tres posibles funciones: agregar, eliminar y ordenar (cuando se desencadena un evento) controladores son posibles desde diferentes subprocesos en momentos aleatorios. Esto crea un campo completo de posibilidades para su "intersección" en el tiempo, "superposición" de su ejecución entre sí y como resultado la caída del programa. Intentemos evitar esto; los mutexes son nuestro todo .

 template<class ...TParams> class TEvent { using TEventHandler = AbstractEventHandler<TParams...>; using TEventHandlerIt = typename std::list<TEventHandler*>::const_iterator; public: TEvent() : m_handlers(), m_currentIt(), m_isCurrentItRemoved( false ), m_handlerListMutex() { } void operator()( TParams... params ) { m_handlerListMutex.lock_shared(); m_isCurrentItRemoved = false; m_currentIt = m_handlers.begin(); while( m_currentIt != m_handlers.end() ) { m_handlerListMutex.unlock_shared(); ( *m_currentIt )->call( params... ); m_handlerListMutex.lock_shared(); if( m_isCurrentItRemoved ) { m_isCurrentItRemoved = false; TEventHandlerIt removedIt = m_currentIt; ++m_currentIt; deleteHandler( removedIt ); } else { ++m_currentIt; } } m_handlerListMutex.unlock_shared(); } bool operator+=( TEventHandler& eventHandler ) { std::unique_lock<std::shared_mutex> _handlerListMutexLock( m_handlerListMutex ); if( findEventHandler( eventHandler ) == m_handlers.end() ) { m_handlers.push_back( std::move( eventHandler ) ); return true; } return false; } bool operator-=( TEventHandler& eventHandler ) { std::unique_lock<std::shared_mutex> _handlerListMutexLock( m_handlerListMutex ); auto it = findEventHandler( eventHandler ); if( it != m_handlers.end() ) { if( it == m_currentIt ) m_isCurrentItRemoved = true; else deleteHandler( it ); return true; } return false; } private: //      'm_handlerListMutex' inline TEventHandlerIt findEventHandler( TEventHandler& eventHandler ) const { return std::find_if( m_handlers.cbegin(), m_handlers.cend(), [ &eventHandler ]( const TEventHandler* oneHandler ) { return ( *oneHandler == eventHandler ); } ); } //      'm_handlerListMutex' inline void deleteHandler( TEventHandlerIt it ) { TEventHandler* removedEventHandler = *it; m_handlers.erase( it ); delete removedEventHandler; } std::list<TEventHandler*> m_handlers; //    'm_handlerListMutex' mutable TEventHandlerIt m_currentIt; mutable bool m_isCurrentItRemoved; mutable std::shared_mutex m_handlerListMutex; }; 

No olvide dejar la ventana abierta casi cada vez que llame a cada controlador. Esto es necesario para que dentro del controlador sea posible acceder al evento y cambiarlo (por ejemplo, agregar / eliminar controladores) sin causar un punto muerto . No puede tener miedo a la validez de los datos, porque, como descubrimos, lo único que lleva a esto es la eliminación del elemento actual, y esta situación se ha procesado.
UPD1. Gracias Cheater , sugirió que std :: shared_mutex solo aparece en C ++ 17 (y std :: shared_lock solo en C ++ 14 ). Aquellos para quienes esto es crítico probablemente tendrán que ver con std :: mutex .
UPD2. Además, sobre la seguridad del hilo (sin preservar la secuencia de la narración).

Problema de visibilidad del evento


Cuando se usa un evento como miembro de una clase, parece lógico hacerlo público , para que los objetos de terceros puedan agregar / eliminar sus controladores. Sin embargo, esto dará como resultado el operador () , es decir También se podrá acceder a una llamada de evento desde el exterior, que en algunos casos puede ser inaceptable. Resolveremos este problema aislando de la clase de evento ( TEvent <...> ) una interfaz abstracta destinada solo para manejar controladores.

 template<class ...TParams> class IEvent { protected: using TEventHandler = AbstractEventHandler<TParams...>; public: bool operator+=( TEventHandler& eventHandler ) { return addHandler( eventHandler ); } bool operator-=( TEventHandler& eventHandler ) { return removeHandler( eventHandler ); } protected: IEvent() {} virtual bool addHandler( TEventHandler& eventHandler ) = 0; virtual bool removeHandler( TEventHandler& eventHandler ) = 0; }; 

 template<class ...TParams> class TEvent : public IEvent<TParams...> { . . . public: TEvent() : IEvent<TParams...>() . . . { } protected: virtual bool addHandler( TEventHandler& eventHandler ) override { // ,     'TEvent::operator+=' } virtual bool removeHandler( TEventHandler& eventHandler ) override { // ,     'TEvent::operator-=' } . . . }; 

Ahora podemos dividir en diferentes ámbitos la parte del evento responsable de trabajar con los controladores y la parte responsable de llamarlo.

 class TestWindow { . . . public: TestWindow() : onButtonClick( m_onButtonClick ), m_onButtonClick() { } IEvent<const std::string&, unsigned int>& onButtonClick; protected: TEvent<const std::string&, unsigned int> m_onButtonClick; . . . }; 

Por lo tanto, ahora los objetos de terceros pueden agregar / eliminar sus controladores a través de TestWindow :: onButtonClick , sin embargo, no podrán activar este evento ellos mismos. Una llamada ahora puede llevarse a cabo solo dentro de la clase TestWindow (y sus descendientes, si el alcance del evento, como ejemplo, está protegido ).

El código trivial lentamente comienza a convertirse en algo monstruoso, pero este no es el final.

Emparejar parámetros de eventos y sus controladores


En la implementación actual, el evento y cualquiera de sus controladores deben tener una lista de parámetros estrictamente correspondiente. Esto lleva a una serie de desventajas.

El primero Supongamos que tenemos una plantilla de clase en la que hay un evento con un parámetro de plantilla.

 template<class TSource> class MyClass { . . . public: TEvent<const TSource&> onValueChanged; . . . }; 

Debido al hecho de que el tipo que se usará aquí no se conoce de antemano, tiene sentido pasarlo por enlace constante, y no por valor. Sin embargo, ahora para cualquier implementación, incluso con tipos fundamentales, los controladores correspondientes deben estar presentes.

 MyClass<bool> myBoolClass; . . . template<class TSource> class MyHandlerClass { . . . private: void handleValueChanged1( const bool& newValue ); void handleValueChanged2( bool newValue ); . . . }; . . . MyHandlerClass myHandlerClass; myBoolClass.onValueChanged += METHOD_HANDLER( myHandlerClass, MyHandlerClass::handleValueChanged1 ); // OK myBoolClass.onValueChanged += METHOD_HANDLER( myHandlerClass, MyHandlerClass::handleValueChanged2 ); // compile error 

Me gustaría poder conectar controladores de la forma MyHandlerClass :: handleValueChanged2 a un evento similar, pero hasta ahora no existe tal posibilidad.

El segundo Intentemos implementar un controlador de functor similar a un método de controlador existente (-function-member de una clase).

 template<class TFunctor, class ...TParams> class FunctorEventHandler : public AbstractEventHandler<TParams...> { public: FunctorEventHandler( TFunctor& functor ) : AbstractEventHandler<TParams...>(), m_functor( functor ) { } virtual void call( TParams... params ) override final { m_functor( params... ); } private: TFunctor& m_functor; }; template<class TFunctor, class ...TParams> AbstractEventHandler<TParams...>& createFunctorEventHandler( TFunctor&& functor ) { return *new FunctorEventHandler<TFunctor, TParams...>( functor ); } #define FUNCTOR_HANDLER( Functor ) createFunctorEventHandler( Functor ) 

Ahora intenta atornillarlo a algún evento.

 class TestWindow { . . . public: TEvent<const std::string&, unsigned int> onButtonClick; . . . }; struct ClickEventHandler { void operator()( const std::string&, unsigned int ) { . . . } }; int main( int argc, char *argv[] ) { . . . TestWindow testWindow; ClickEventHandler clickEventHandler; testWindow.onButtonClick += FUNCTOR_HANDLER( clickEventHandler ); . . . } 

El resultado será un error de compilación. Para la función createFunctorEventHandler, el compilador no puede inferir los tipos de TParams ... a partir del único argumento de esta función: el propio functor. El functor realmente no contiene ninguna información sobre qué tipo de controlador crear en base a él. Lo único que se puede hacer en esta situación es escribir algo como:

 testWindow.onButtonClick += createFunctorEventHandler<ClickEventHandler, const std::string&, unsigned int>( clickEventHandler ); 

Pero no quieres hacer esto en absoluto.

Conectar un evento a varios tipos de controladores


Entonces, hay una lista de deseos, depende de la implementación. Consideraremos la situación utilizando el ejemplo de un controlador de functor; se obtendrá un método de controlador (-function-member de una clase) de manera similar.

Dado que solo con un functor es imposible decir cuál será la lista de parámetros del controlador correspondiente, entonces no haremos esto. Esta pregunta se vuelve relevante no en el momento en que se creó el controlador, sino en el momento de intentar adjuntarlo a un evento específico. Y sí, estos son dos puntos diferentes. Esta idea se puede implementar de la siguiente manera:

 template<class TFunctor> class FunctorHolder; template<class TFunctor, class ...TParams> class FunctorEventHandler : public AbstractEventHandler<TParams...> { public: FunctorEventHandler( FunctorHolder<TFunctor>& functorHolder ) : AbstractEventHandler<TParams...>(), m_functorHolder( functorHolder ) { } virtual void call( TParams... params ) override { m_functorHolder.m_functor( params... ); } private: FunctorHolder<TFunctor>& m_functorHolder; . . . }; 

 template<class TFunctor> class FunctorHolder { public: FunctorHolder( TFunctor& functor ) : m_functor( functor ) { } template<class ...TCallParams> operator AbstractEventHandler<TCallParams...>&() { return *new FunctorEventHandler<TFunctor, TCallParams...>( *this ); } private: TFunctor& m_functor; . . . template<class TFunctor, class ...TParams> friend class FunctorEventHandler; }; 

 template<class TFunctor> FunctorHolder<TFunctor>& createFunctorEventHandler( TFunctor&& functor ) { return *new FunctorHolder<TFunctor>( functor ); } #define FUNCTOR_HANDLER( Functor ) createFunctorEventHandler( Functor ) #define LAMBDA_HANDLER( Lambda ) FUNCTOR_HANDLER( Lambda ) #define STD_FUNCTION_HANDLER( StdFunction ) FUNCTOR_HANDLER( StdFunction ) #define FUNCTION_HANDLER( Function ) FUNCTOR_HANDLER( &Function ) 

 template<class ...TParams> class IEvent { protected: using TEventHandler = AbstractEventHandler<TParams...>; public: template<class TSome> bool operator+=( TSome&& some ) { return addHandler( static_cast<TEventHandler&>( some ) ); } template<class TSome> bool operator-=( TSome&& some ) { return removeHandler( static_cast<TEventHandler&>( some ) ); } protected: IEvent() {} virtual bool addHandler( TEventHandler& eventHandler ) = 0; virtual bool removeHandler( TEventHandler& eventHandler ) = 0; }; 

En resumen, la separación de los momentos de la creación del controlador y su apego al evento aquí es más pronunciada que antes. Esto evita los problemas descritos en el párrafo anterior. La prueba de compatibilidad de tipos ocurrirá cuando intente adjuntar un FunctorHolder específico a un FunctorEventHandler específico, o más bien, cree una instancia de la Clase FunctorEventHandler <...> con un tipo de functor muy específico; y en esta clase habrá una línea de código m_functorHolder.m_functor (params ...); , que simplemente no se puede compilar para un conjunto de tipos incompatibles con un functor (o si no lo es, es decir, un objeto que no tiene operador () ).

Repito que el problema de eliminar objetos temporales se discutirá a continuación. Además, vale la pena señalar que se hicieron un montón de macros para cada caso, en primer lugar, para demostrar las capacidades de este tipo de controladores, y en segundo lugar, en caso de una posible modificación de cualquiera de ellos con un archivo.

Comprueba el resultado.

 class TestWindow { . . . public: TEvent<const std::string&, unsigned int> onButtonClick; . . . }; struct Functor { void operator()( const std::string&, unsigned int ) {} }; struct Functor2 { void operator()( std::string, unsigned int ) {} }; struct Functor3 { void operator()( const std::string&, const unsigned int& ) {} }; struct Functor4 { void operator()( std::string, const unsigned int& ) {} }; struct Functor5 { void operator()( std::string&, unsigned int& ) {} }; struct Functor6 { void operator()( const std::string&, unsigned int& ) {} }; struct Functor7 { void operator()( std::string&, const unsigned int& ) {} }; int main( int argc, char *argv[] ) { . . . TestWindow testWindow; Functor functor; Functor2 functor2; Functor3 functor3; Functor4 functor4; Functor5 functor5; Functor6 functor6; Functor7 functor7; testWindow.onButtonClick += FUNCTOR_HANDLER( functor ); // ok testWindow.onButtonClick += FUNCTOR_HANDLER( functor2 ); // ok testWindow.onButtonClick += FUNCTOR_HANDLER( functor3 ); // ok testWindow.onButtonClick += FUNCTOR_HANDLER( functor4 ); // ok testWindow.onButtonClick += FUNCTOR_HANDLER( functor5 ); // compile error testWindow.onButtonClick += FUNCTOR_HANDLER( functor6 ); // ok testWindow.onButtonClick += FUNCTOR_HANDLER( functor7 ); // compile error . . . } 

Se produce un error de compilación al intentar convertir uno de los parámetros de const lvalue a lvalue . La conversión de rvalue a inconst lvalue no causa un error, aunque vale la pena señalar que crea una amenaza potencial de auto-disparo en la pierna: el controlador podrá cambiar la variable copiada en la pila, que se eliminará alegremente cuando este controlador salga.

En general, el mensaje de error debería verse así:

 Error C2664 'void Functor5::operator ()(std::string &,unsigned int &)': cannot convert argument 1 from 'const std::string' to 'std::string &' 

Para mayor claridad, al usar eventos y controladores en código de terceros, puede agregar su propio mensaje de error. Esto requerirá escribir una pequeña estructura de soporte (lo admito, vi un enfoque similar en alguna parte):

 namespace { template<class TFunctor, class ...TParams> struct IsFunctorParamsCompatible { private: template<class TCheckedFunctor, class ...TCheckedParams> static constexpr std::true_type exists( decltype( std::declval<TCheckedFunctor>()( std::declval<TCheckedParams>()... ) )* = nullptr ); template<class TCheckedFunctor, class ...TCheckedParams> static constexpr std::false_type exists( ... ); public: static constexpr bool value = decltype( exists<TFunctor, TParams...>( nullptr ) )::value; }; } // 

 template<class TFunctor, class ...TParams> class FunctorEventHandler : public AbstractEventHandler<TParams...> { . . . public: virtual void call( TParams... params ) override { static_assert( IsFunctorParamsCompatible<TFunctor, TParams...>::value, "Event and functor arguments are not compatible" ); m_functorHolder->m_functor( params... ); } . . . }; 

Este trabajo se basa en el mecanismo SFINAE . En resumen, se intenta compilar la primera función, sin embargo, si esto no funciona debido a la incompatibilidad de los argumentos (o la ausencia de operador () de lo que se pasó como functor), el compilador no arroja un error, sino que simplemente intenta compilar la segunda función; Hacemos todo lo posible para que su compilación siempre tenga éxito, y luego, sobre el hecho de qué función se compiló, concluimos (escribiendo el resultado en valor ) sobre la compatibilidad de los argumentos para los tipos dados.

Ahora el mensaje de error se verá así:

 Error C2338 Event and functor arguments are not compatible Error C2664 'void Functor5::operator ()(std::string &,unsigned int &)': cannot convert argument 1 from 'const std::string' to 'std::string &' 

Además de un mensaje de error adicional y más informativo, este enfoque resuelve el problema de convertir los argumentos de rvalue a inconst lvalue : ahora provoca un error de incompatibilidad de argumentos, es decir. Intentar agregar el controlador functor6 del ejemplo anterior produce un error en tiempo de compilación.
UPD Refinamiento (sin preservar la secuencia de la narración).

Comparación de Functor


Debido a los cambios en la clase de controlador, la implementación de la comparación de instancias de esta clase cambiará ligeramente. Una vez más, proporcionaré una implementación de solo un controlador de functor, porque el método de controlador (-function-member de la clase) se verá similar.

 template<class ...TParams> class AbstractEventHandler { . . . using MyType = AbstractEventHandler<TParams...>; public: bool operator==( const MyType& other ) const { return isEquals( other ); } bool operator!=( const MyType& other ) const { return !( *this == other ); } protected: virtual bool isEquals( const MyType& other ) const = 0; . . . }; 

 template<class TFunctor, class ...TParams> class FunctorEventHandler : public AbstractEventHandler<TParams...> { . . . using MyType = FunctorEventHandler<TFunctor, TParams...>; protected: virtual bool isEquals( const AbstractEventHandler<TParams...>& other ) const override { const MyType* _other = dynamic_cast<const MyType*>( &other ); return ( _other != nullptr && *m_functorHolder == *_other->m_functorHolder ); } private: FunctorHolder<TFunctor>& m_functorHolder; . . . }; 

 template<class TFunctor> class FunctorHolder { . . . using MyType = FunctorHolder<TFunctor>; public: bool operator==( const MyType& other ) const { return ( m_functor == other.m_functor ); } bool operator!=( const MyType& other ) const { return !( *this == other ); } private: TFunctor& m_functor; . . . }; 

En esto, las similitudes en la implementación del final de comparación y la parte comienzan solo para los manejadores de functor.

Como se señaló anteriormente, hemos obtenido varios tipos de manejadores de functor: directamente objetos de functor, expresiones lambda, instancias de la clase std :: function , funciones individuales. De estos, los objetos de functor, las expresiones lambda y las instancias de la clase std :: function no se pueden comparar usando operator == (deben compararse en la dirección), pero las funciones individuales sí, porque ya almacenado en. Para no reescribir la función de comparación por separado para cada caso, la escribimos de forma general:

 namespace { template<class TEqu, class TEnabled = void> struct EqualityChecker; template<class TEquatable> struct EqualityChecker<TEquatable, typename std::enable_if<is_equatable<TEquatable>::value>::type> { static constexpr bool isEquals( const TEquatable& operand1, const TEquatable& operand2 ) { return ( operand1 == operand2 ); } }; template<class TNonEquatable> struct EqualityChecker<TNonEquatable, typename std::enable_if<!is_equatable<TNonEquatable>::value>::type> { static constexpr bool isEquals( const TNonEquatable& operand1, const TNonEquatable& operand2 ) { return ( &operand1 == &operand2 ); } }; } // template<class TFunctor> class FunctorHolder { . . . using MyType = FunctorHolder<TFunctor>; public: bool operator==( const MyType& other ) const { return EqualityChecker<TFunctor>::isEquals( m_functor, other.m_functor ); } private: TFunctor& m_functor; . . . }; 

Se entiende que is_equatable es una plantilla auxiliar que determina si dos instancias de un tipo dado pueden ser verificadas por igualdad. Con su ayuda, usando std :: enable_if , seleccionamos una de las dos estructuras EqualityChecker parcialmente especializadas , que realizarán una comparación: por valor o por dirección. Se implementa Is_equatable, puede ser el siguiente:

 template<class T> class is_equatable { private: template<class U> static constexpr std::true_type exists( decltype( std::declval<U>() == std::declval<U>() )* = nullptr ); template<class U> static constexpr std::false_type exists( ... ); public: static constexpr bool value = decltype( exists<T>( nullptr ) )::value; }; 

Esta implementación se basa en el mecanismo SFINAE , que ya se ha utilizado anteriormente . Solo aquí verificamos la presencia del operador == para instancias de una clase dada.

De esta manera simple, la implementación de la comparación de manejadores-functores está lista.

Recolección de basura


Sea indulgente, también quería insertar un titular ruidoso.

Nos estamos acercando al final, y es hora de deshacerse de la gran cantidad de objetos creados que nadie controla.

Cada acción de evento con un controlador crea dos objetos: Holder , que almacena la parte ejecutable del controlador, y EventHandlerconectándolo con el evento. No olvide que en el caso de un intento de volver a agregar el controlador, no ocurrirá ninguna adición: dos objetos están "suspendidos en el aire" (a menos, por supuesto, que no marque este caso por separado cada vez). Otra situación: eliminar el controlador; también se crean dos nuevos objetos para buscar el mismo (igual) en la lista de controladores de eventos; el controlador encontrado de la lista, por supuesto, se elimina (si lo hay), y este temporal, creado para la búsqueda y que consta de dos objetos, está nuevamente "en el aire". En general, no es genial.

Dirígete a punteros inteligentes . Necesitamos determinar cuál será la semántica de propiedad de cada uno de los dos objetos del controlador: propiedad única ( std :: unique_ptr ) o compartida ( std :: shared_ptr ).

Titular, además de usar el evento en sí mismo al agregarlo / eliminarlo, debe almacenarse en EventHandler , por lo tanto, lo usamos para la propiedad compartida, y para EventHandler es único, porque después de la creación, se almacenará solo en la lista de controladores de eventos.

Nos damos cuenta de esta idea:

 template<class ...TParams> class AbstractEventHandler { . . . public: virtual ~AbstractEventHandler() {} . . . }; template<class ...Types> using THandlerPtr = std::unique_ptr<AbstractEventHandler<Types...>>; 

 namespace { template<class TSome> struct HandlerCast { template<class ...Types> static constexpr THandlerPtr<Types...> cast( TSome& some ) { return static_cast<THandlerPtr<Types...>>( some ); } }; template<class TPtr> struct HandlerCast<std::shared_ptr<TPtr>> { template<class ...Types> static constexpr THandlerPtr<Types...> cast( std::shared_ptr<TPtr> some ) { return HandlerCast<TPtr>::cast<Types...>( *some ); } }; } // template<class ...TParams> class IEvent { public: template<class TSome> bool operator+=( TSome&& some ) { return addHandler( HandlerCast<TSome>::cast<TParams...>( some ) ); } template<class TSome> bool operator-=( TSome&& some ) { return removeHandler( HandlerCast<TSome>::cast<TParams...>( some ) ); } protected: using TEventHandlerPtr = THandlerPtr<TParams...>; IEvent() {} virtual bool addHandler( TEventHandlerPtr eventHandler ) = 0; virtual bool removeHandler( TEventHandlerPtr eventHandler ) = 0; }; template<class ...TParams> class TEvent : public IEvent<TParams...> { using TEventHandlerIt = typename std::list<TEventHandlerPtr>::const_iterator; public: TEvent() { . . . } ~TEvent() { // empty } protected: virtual bool addHandler( TEventHandlerPtr eventHandler ) override { std::unique_lock<std::shared_mutex> _handlerListMutexLock( m_handlerListMutex ); if( findEventHandler( eventHandler ) == m_handlers.end() ) { m_handlers.push_back( std::move( eventHandler ) ); return true; } return false; } virtual bool removeHandler( TEventHandlerPtr eventHandler ) override { . . . } private: //      'm_handlerListMutex' inline TEventHandlerIt findEventHandler( const TEventHandlerPtr& eventHandler ) const { return std::find_if( m_handlers.cbegin(), m_handlers.cend(), [ &eventHandler ]( const TEventHandlerPtr& oneHandler ) { return ( *oneHandler == *eventHandler ); } ); } //      'm_handlerListMutex' inline void deleteHandler( TEventHandlerIt it ) { m_handlers.erase( it ); } std::list<TEventHandlerPtr> m_handlers; . . . }; 

 template<class TMethodHolder, class ...TParams> class MethodEventHandler : public AbstractEventHandler<TParams...> { . . . using TMethodHolderPtr = std::shared_ptr<TMethodHolder>; public: MethodEventHandler( TMethodHolderPtr methodHolder ) : AbstractEventHandler<TParams...>(), m_methodHolder( methodHolder ) { assert( m_methodHolder != nullptr ); } private: TMethodHolderPtr m_methodHolder; . . . }; template<class TObject, class ...TParams> class MethodHolder { using MyType = MethodHolder<TObject, TParams...>; using TMethod = void( TObject::* )( TParams... ); public: MethodHolder( TObject& object, TMethod method ) { . . . } template<class ...TCallParams> operator THandlerPtr<TCallParams...>() { return THandlerPtr<TCallParams...>( new MethodEventHandler<MyType, TCallParams...>( /*   ? */ ) ); } . . . }; template<class TObject, class ...TParams> std::shared_ptr<MethodHolder<TObject, TParams...>> createMethodEventHandler( TObject& object, void( TObject::*method )( TParams... ) ) { return std::shared_ptr<MethodHolder<TObject, TParams...>>( new MethodHolder<TObject, TParams...>( object, method ) ); } #define METHOD_HANDLER( Object, Method ) createMethodEventHandler( Object, &Method ) #define MY_METHOD_HANDLER( Method ) METHOD_HANDLER( *this, Method ) 

Lo primero es lo primero.

Para comenzar, el evento y su interfaz para trabajar con controladores. En el último, ya no puede convertir tipos directamente usando static_cast , porque el tipo convertido se encuentra "dentro" std :: shared_ptr . Ahora, para tal conversión, usaremos la estructura auxiliar HandlerCast , que, por su especialización privada, proporcionará acceso al objeto dentro de std :: shared_ptr , y ya trabajando con él (en su implementación no especializada), aplicará el viejo static_cast .

El evento en sí mismo; Hay varios cambios importantes aquí también. Primero, dejaremos de eliminar manualmente las instancias del controlador en el destructor y al eliminarlas; ahora es suficiente eliminar el puntero inteligente con este controlador de la lista. Además, al agregar un controlador, es importante no olvidar std :: move , porque std :: unique_ptr no admite la copia (lo cual es bastante lógico para dicha semántica).

Pasemos a los manejadores. Según la antigua tradición, solo se da uno de ellos, el segundo es similar. Y aquí, a primera vista, todo se reduce a cambiar los tipos de objetos almacenados / creados de enlaces / punteros a punteros inteligentes.

Pero hay un punto sutil. La función createMethodEventHandler devolverá std :: shared_ptr a una instanciaMétodo Titular . Un poco más tarde, se intentará convertirlo a un tipo de controlador ( MethodEventHandler ), donde tendrá que crear una nueva instancia de MethodEventHandler , pasándola al constructor std :: shared_ptr en sí mismo. Esa es la forma en que estaba destinado a la instancia MethodHolder'a tarde eliminado al desinstalar una instancia MethodEventHandler'a . Pero el problema es que MethodHolder no tiene acceso al std :: shared_ptr ya creado que lo almacena.

Para resolver el problema, debe almacenar un puntero inteligente en MethodHolder . Sin embargo, para que no afecte su eliminación, utilizamosstd :: weak_ptr :

 template<class TObject, class ...TParams> class MethodHolder { using MyType = MethodHolder<TObject, TParams...>; using TMethod = void( TObject::* )( TParams... ); public: template<class ...TCallParams> operator THandlerPtr<TCallParams...>() { return THandlerPtr<TCallParams...>( new MethodEventHandler<MyType, TCallParams...>( m_me.lock() ) ); } template<class TObject, class ...TParams> static std::shared_ptr<MyType> create( TObject& object, TMethod method ) { std::shared_ptr<MyType> result( new MyType( object, method ) ); result->m_me = result; return result; } private: MethodHolder( TObject& object, TMethod method ) : m_object( object ), m_method( method ) { assert( m_method != nullptr ); } TObject& m_object; TMethod m_method; std::weak_ptr<MyType> m_me; }; template<class TObject, class ...TParams> std::shared_ptr<MethodHolder<TObject, TParams...>> createMethodEventHandler( TObject& object, void( TObject::*method )( TParams... ) ) { return MethodHolder<TObject, TParams...>::create( object, method ); } 

Para mayor claridad, daré un orden aproximado de eventos al eliminar un controlador de un evento (mis disculpas por un juego de palabras al azar):

  • el evento elimina el elemento de la lista ( m_handlers.erase (it); ), lo que hace que se llame a su destructor;
  • se llama al destructor std :: unique_ptr , que conduce a una llamada al destructor del objeto administrado;
  • Se llama al destructor MethodEventHandler , que elimina todos los campos del objeto, incluido el campo m_methodHolder , que es std :: shared_ptr ;
  • std::shared_ptr ; , (.. ) ( MethodHolder ); , std::weak_ptr ;
  • MethodHolder , , , m_me , std::weak_ptr ;
  • std::weak_ptr ; ; porque std::weak_ptr , ;
  • .

Es importante recordar que el destructor de la clase AbstractEventHandler debe ser virtual; de lo contrario, después de la cláusula 2 en la cláusula 3 , se llamará al destructor AbstractEventHandler y no se realizarán más acciones.

Conexión de evento y controlador


En algunos casos, cuando agregar / eliminar un controlador de un evento ocurre con bastante frecuencia (de acuerdo con alguna lógica), no desea molestarse, obtener una instancia del evento y una instancia del controlador cada vez, para implementar una vez más una suscripción / cancelación de suscripción de este evento. Pero quiero conectarlos una vez, y luego, si es necesario, trabajar con esta conexión, agregando / eliminando un controlador predefinido de un evento predefinido. Puede implementar esto de la siguiente manera:

 template<class ...Types> using THandlerPtr = std::shared_ptr<AbstractEventHandler<Types...>>; 

 template<class ...TParams> class IEvent { . . . protected: using TEventHandlerPtr = THandlerPtr<TParams...>; virtual bool isHandlerAdded( const TEventHandlerPtr& eventHandler ) const = 0; virtual bool addHandler( TEventHandlerPtr eventHandler ) = 0; virtual bool removeHandler( TEventHandlerPtr eventHandler ) = 0; friend class HandlerEventJoin<TParams...>; . . . }; template<class ...TParams> class TEvent : public IEvent<TParams...> { . . . protected: virtual bool isHandlerAdded( const TEventHandlerPtr& eventHandler ) const override { std::shared_lock<std::shared_mutex> _handlerListMutexLock( m_handlerListMutex ); return ( findEventHandler( eventHandler ) != m_handlers.end() ); } virtual bool addHandler( TEventHandlerPtr eventHandler ) override { . . . } virtual bool removeHandler( TEventHandlerPtr eventHandler ) override { . . . } private: //      'm_handlerListMutex' inline TEventHandlerIt findEventHandler( const TEventHandlerPtr& eventHandler ) const { . . . } std::list<TEventHandlerPtr> m_handlers; mutable std::shared_mutex m_handlerListMutex; . . . }; 

 template<class ...TParams> class HandlerEventJoin { public: HandlerEventJoin( IEvent<TParams...>& _event, THandlerPtr<TParams...> handler ) : m_event( _event ), m_handler( handler ) { } inline bool isJoined() const { return m_event.isHandlerAdded( m_handler ); } inline bool join() { return m_event.addHandler( m_handler ); } inline bool unjoin() { return m_event.removeHandler( m_handler ); } private: IEvent<TParams...>& m_event; THandlerPtr<TParams...> m_handler; }; 

Como puede ver, ahora se ha agregado otro lugar posible para almacenar la instancia del controlador, por lo que usaremos std :: shared_ptr en lugar de std :: unique_ptr para esto .

Sin embargo, esta clase, para mí, es un poco incómoda de usar. Me gustaría almacenar y crear instancias de conexión sin una lista de parámetros que instancian la plantilla de clase.

Implementamos esto usando una clase ancestral abstracta y un contenedor:

 class AbstractEventJoin { public: virtual ~AbstractEventJoin() {} virtual bool isJoined() const = 0; virtual bool join() = 0; virtual bool unjoin() = 0; protected: AbstractEventJoin() {} }; 

 template<class ...TParams> class HandlerEventJoin : public AbstractEventJoin { . . . public: virtual inline bool isJoined() const override { . . . } virtual inline bool join() override { . . . } virtual inline bool unjoin() override { . . . } . . . }; 

 class EventJoinWrapper { public: template<class TSome, class ...TParams> inline EventJoinWrapper( IEvent<TParams...>& _event, TSome&& handler ) : m_eventJoin( std::make_shared<HandlerEventJoin<TParams...>>( _event, HandlerCast<TSome>::cast<TParams...>( handler ) ) ) { } constexpr EventJoinWrapper() : m_eventJoin( nullptr ) { } ~EventJoinWrapper() { if( m_eventJoin != nullptr ) delete m_eventJoin; } operator bool() const { return isJoined(); } bool isAssigned() const { return ( m_eventJoin != nullptr ); } bool isJoined() const { return ( m_eventJoin != nullptr && m_eventJoin->isJoined() ); } bool join() { return ( m_eventJoin != nullptr ? m_eventJoin->join() : false ); } bool unjoin() { return ( m_eventJoin != nullptr ? m_eventJoin->unjoin() : false ); } private: AbstractEventJoin* m_eventJoin; }; using EventJoin = EventJoinWrapper; 

HandlerCast es la misma estructura de soporte que se usó aquí . Por cierto, es importante no olvidar hacer virtual el destructor AbstractEventJoin para que cuando elimine su instancia en el destructor EventJoinWrapper , se llame al destructor HandlerEventJoin , de lo contrario, el campo THandlerPtr y, por lo tanto, el controlador en sí no se destruirá .

Esta implementación parece ser viable, pero solo a primera vista. Copiar o mover una instancia de EventJoinWrapper eliminará m_eventJoin en su destructor nuevamente . Por lo tanto, usamos std :: shared_ptr para almacenar la instanciaAbstractEventJoin , así como también implementa una semántica de movimiento (y copia) ligeramente optimizada, porque Esta será una operación potencialmente frecuente.

 class EventJoinWrapper { public: EventJoinWrapper( EventJoinWrapper&& other ) : m_eventJoin( std::move( other.m_eventJoin ) ) { } EventJoinWrapper( EventJoinWrapper& other ) : m_eventJoin( other.m_eventJoin ) { } ~EventJoinWrapper() { /*empty*/ } EventJoinWrapper& operator=( EventJoinWrapper&& other ) { m_eventJoin = std::move( other.m_eventJoin ); return *this; } EventJoinWrapper& operator=( const EventJoinWrapper& other ) { m_eventJoin = other.m_eventJoin; return *this; } . . . private: std::shared_ptr<AbstractEventJoin> m_eventJoin; }; 

Ahora, al conectar un controlador a un evento, puede devolver inmediatamente una instancia de una nueva conexión:

 template<class ...TParams> class IEvent { . . . public: template<class TSome> EventJoin operator+=( TSome&& some ) { EventJoin result( *this, std::forward<TSome>( some ) ); result.join(); return result; } . . . }; 

Y después de resolver la dependencia triangular mediante include (IEvent <= EventJointWrapper.hpp; EventJointWrapper <= HandlerEventJoin.hpp; HandlerEventJoin <= IEvent.hpp) dividiendo algunos archivos en .h y .hpp , incluso puede trabajar con esto.

La creación de instancias de conexión sigue las mismas reglas que funcionan cuando el controlador de eventos se suscribe:

 struct EventHolder { TEvent<const std::string&> onEvent; }; struct MethodsHolder { void method1( const std::string& ) {} void method2( std::string ) {} void method3( std::string&& ) {} void method4( std::string& ) {} void method5( const int& ) {} }; int main( int argc, char* argv[] ) { EventHolder _eventHolder; MethodsHolder _methodsHolder; EventJoin join1 = EventJoin( _eventHolder.onEvent, METHOD_HANDLER( _methodsHolder, MethodsHolder::method1 ) ); // ok EventJoin join2 = EventJoin( _eventHolder.onEvent, METHOD_HANDLER( _methodsHolder, MethodsHolder::method2 ) ); // ok EventJoin join3 = EventJoin( _eventHolder.onEvent, METHOD_HANDLER( _methodsHolder, MethodsHolder::method3 ) ); // error EventJoin join4 = EventJoin( _eventHolder.onEvent, METHOD_HANDLER( _methodsHolder, MethodsHolder::method4 ) ); // error EventJoin join5 = EventJoin( _eventHolder.onEvent, METHOD_HANDLER( _methodsHolder, MethodsHolder::method5 ) ); // error return 0; } 

Además, puede "habilitar" / "deshabilitar" el procesamiento de eventos (para lo cual, en principio, se crearon conexiones):

 struct EventHolder { TEvent<const std::string&, unsigned int> onEvent; }; struct MethodsHolder { void handleEvent( const std::string& text, unsigned int count ) { std::cout << "Text '" << text << "' handled " << count << " times." << std::endl; } }; int main__( int argc, char* argv[] ) { EventHolder _eventHolder; MethodsHolder methodsHolder; EventJoin eventJoin = EventJoin( _eventHolder.onEvent, METHOD_HANDLER( methodsHolder, MethodsHolder::handleEvent ) ); static const std::string handlingText = "testing..."; for( int i = 0; i < 10; ++i ) { if( eventJoin.isJoined() ) eventJoin.unjoin(); else eventJoin.join(); _eventHolder.onEvent( handlingText, i ); } return 0; } 

 Text 'testing...' handled 0 times. Text 'testing...' handled 2 times. Text 'testing...' handled 4 times. Text 'testing...' handled 6 times. Text 'testing...' handled 8 times. 

Resumen


Primero, vale la pena señalar que la tarea de escribir un artículo lo más breve posible y sucinta falló por completo.

Espero que la implementación resultante del procesamiento de eventos sea bastante funcional y sea útil al menos para alguien.

Un ejemplo muy engorroso que demuestra las características principales.
 #include <iostream> #include <functional> #include "events/event.hpp" #include "events/handler/methodeventhandler.hpp" #include "events/handler/functoreventhandler.hpp" #include "events/join/handlereventjoin.hpp" #include "events/join/eventjoinwrapper.hpp" class Foo { public: Foo() : onMake( m_onMake ), m_onMake(), m_onMakeInner(), m_makeCount( 0 ) { m_onMakeInner += FUNCTOR_HANDLER( m_onMake ); } IEvent<unsigned int>& onMake; void make() { m_onMakeInner( m_makeCount++ ); } private: TEvent<unsigned int> m_onMake, m_onMakeInner; unsigned int m_makeCount; }; namespace instances { Foo& getFoo() { static Foo _foo; return _foo; } } // instances struct FunctorHandler { void operator()( unsigned int makeCount ); }; void functionHandler( unsigned int makeCount ); class ClassHandler { public: void handle( unsigned int makeCount ); }; namespace instances { FunctorHandler& getFunctorHandler() { static FunctorHandler _functorHandler; return _functorHandler; } std::function<void( unsigned int )>& getStdFunctionHandler() { static std::function<void( unsigned int )> _stdFunctionHandler = []( unsigned int makeCount ) { std::cout << "It's std::function handler" << std::endl; if( makeCount >= 2 ) instances::getFoo().onMake -= STD_FUNCTION_HANDLER( instances::getStdFunctionHandler() ); }; return _stdFunctionHandler; } ClassHandler& getClassHandler() { static ClassHandler _classHandler; return _classHandler; } } // instances void FunctorHandler::operator()( unsigned int makeCount ) { std::cout << "It's functor handler" << std::endl; if( makeCount >= 0 ) instances::getFoo().onMake -= FUNCTOR_HANDLER( instances::getFunctorHandler() ); } void functionHandler( unsigned int makeCount ) { std::cout << "It's function handler" << std::endl; if( makeCount >= 3 ) instances::getFoo().onMake -= FUNCTION_HANDLER( functionHandler ); } void ClassHandler::handle( unsigned int makeCount ) { std::cout << "It's method handler" << std::endl; if( makeCount >= 4 ) instances::getFoo().onMake -= MY_METHOD_HANDLER( ClassHandler::handle ); } int main( int argc, char* argv[] ) { Foo& foo = instances::getFoo(); auto lambdaHandler = []( unsigned int ) { std::cout << "It's lambda handler" << std::endl; }; foo.onMake += FUNCTOR_HANDLER( instances::getFunctorHandler() ); foo.onMake += LAMBDA_HANDLER( lambdaHandler ); EventJoin lambdaJoin = foo.onMake += LAMBDA_HANDLER( ( [ &foo, &lambdaHandler ]( unsigned int makeCount ) { if( makeCount >= 1 ) foo.onMake -= LAMBDA_HANDLER( lambdaHandler ); } ) ); foo.onMake += STD_FUNCTION_HANDLER( instances::getStdFunctionHandler() ); foo.onMake += FUNCTION_HANDLER( functionHandler ); foo.onMake += METHOD_HANDLER( instances::getClassHandler(), ClassHandler::handle ); for( int i = 0; i < 6; ++i ) { std::cout << "Make " << i << " time:" << std::endl; foo.make(); std::cout << std::endl; } lambdaJoin.unjoin(); return 0; } 

Conclusión

 Make 0 time: It's functor handler It's lambda handler It's std::function handler It's function handler It's method handler Make 1 time: It's lambda handler It's std::function handler It's function handler It's method handler Make 2 time: It's std::function handler It's function handler It's method handler Make 3 time: It's function handler It's method handler Make 4 time: It's method handler Make 5 time: 


Vale la pena señalar una serie de puntos importantes:

  • no se especificó por separado, por lo que mencionaré que el evento en sí en esta implementación es un functor, lo que significa que puede actuar como un controlador para otro evento;
  • ahora no puede usar métodos constantes (funciones de miembro de clase) como controladores; Creo que si se necesita esa oportunidad, no es difícil escribir un nuevo tipo de controlador basado en los existentes.

Además, en la versión final hay algunos puntos omitidos en el artículo para una mayor visibilidad y legibilidad del código:

  • el tipo del valor de retorno del método (función miembro de la clase) para el manejador correspondiente puede ser cualquiera, no necesariamente nulo (para manejadores-functores, esto también se hizo);
  • toda la implementación está envuelta en espacios de nombres para facilitar su uso en proyectos (si esto le parece superfluo a alguien, siempre puede eliminarlos);
  • El especificador noexcept se ha agregado en algunos lugares .

Para todos aquellos que han leído aquí, al menos en diagonal, un arco bajo. Adjunto todo el código; También se puede tomar aquí (con todas las últimas mejoras).

Código completo
./events/helpers/is_equatable.hpp
 #pragma once #include <type_traits> template<class T> class is_equatable { private: template<class U> static constexpr std::true_type exists( decltype( std::declval<U>() == std::declval<U>() )* = nullptr ) noexcept; template<class U> static constexpr std::false_type exists( ... ) noexcept; public: static constexpr bool value = decltype( exists<T>( nullptr ) )::value; }; 


./events/handlers/abstracteventhandler.hpp
 #pragma once #include "eventhandlerptr.h" namespace events { namespace handlers { template<class ...TParams> class AbstractEventHandler { using MyType = AbstractEventHandler<TParams...>; public: virtual ~AbstractEventHandler() {} virtual void call( TParams... params ) = 0; bool operator==( const MyType& other ) const noexcept { return isEquals( other ); } bool operator!=( const MyType& other ) const noexcept { return !( *this == other ); } protected: AbstractEventHandler() {} virtual bool isEquals( const MyType& other ) const noexcept = 0; }; } // handlers } // events 


./events/handlers/eventhandlerptr.h
 #pragma once #include <memory> namespace events { namespace handlers { template<class ...TParams> class AbstractEventHandler; template<class ...Types> using TEventHandlerPtr = std::shared_ptr<AbstractEventHandler<Types...>>; } // handlers } // events 


./events/handlers/functoreventhandler.hpp
 #pragma once #include <memory> #include <assert.h> #include "abstracteventhandler.hpp" #include "../helpers/is_equatable.hpp" namespace events { namespace handlers { namespace { template<class TFunctor, class ...TParams> struct IsFunctorParamsCompatible { private: template<class TCheckedFunctor, class ...TCheckedParams> static constexpr std::true_type exists( decltype( std::declval<TCheckedFunctor>()( std::declval<TCheckedParams>()... ) )* = nullptr ) noexcept; template<class TCheckedFunctor, class ...TCheckedParams> static constexpr std::false_type exists( ... ) noexcept; public: static constexpr bool value = decltype( exists<TFunctor, TParams...>( nullptr ) )::value; }; } // template<class TFunctor> class FunctorHolder; template<class TFunctor, class ...TParams> class FunctorEventHandler : public AbstractEventHandler<TParams...> { using MyType = FunctorEventHandler<TFunctor, TParams...>; using TFunctorHolderPtr = std::shared_ptr<FunctorHolder<TFunctor>>; public: FunctorEventHandler( TFunctorHolderPtr functorHolder ) : AbstractEventHandler<TParams...>(), m_functorHolder( functorHolder ) { assert( m_functorHolder != nullptr ); } virtual void call( TParams... params ) override { static_assert( IsFunctorParamsCompatible<TFunctor, TParams...>::value, "Event and functor arguments are not compatible" ); m_functorHolder->m_functor( params... ); } protected: virtual bool isEquals( const AbstractEventHandler<TParams...>& other ) const noexcept override { const MyType* _other = dynamic_cast<const MyType*>( &other ); return ( _other != nullptr && *m_functorHolder == *_other->m_functorHolder ); } private: TFunctorHolderPtr m_functorHolder; }; namespace { template<class TEqu, class TEnabled = void> struct EqualityChecker; template<class TEquatable> struct EqualityChecker<TEquatable, typename std::enable_if<is_equatable<TEquatable>::value>::type> { static constexpr bool isEquals( const TEquatable& operand1, const TEquatable& operand2 ) noexcept { return ( operand1 == operand2 ); } }; template<class TNonEquatable> struct EqualityChecker<TNonEquatable, typename std::enable_if<!is_equatable<TNonEquatable>::value>::type> { static constexpr bool isEquals( const TNonEquatable& operand1, const TNonEquatable& operand2 ) noexcept { return ( &operand1 == &operand2 ); } }; } // template<class TFunctor> class FunctorHolder { using MyType = FunctorHolder<TFunctor>; public: template<class ...TCallParams> operator TEventHandlerPtr<TCallParams...>() { return TEventHandlerPtr<TCallParams...>( new FunctorEventHandler<TFunctor, TCallParams...>( m_me.lock() ) ); } bool operator==( const MyType& other ) const noexcept { return EqualityChecker<TFunctor>::isEquals( m_functor, other.m_functor ); } bool operator!=( const MyType& other ) const noexcept { return !( *this == other ); } template<class TFunctor> static std::shared_ptr<MyType> create( TFunctor&& functor ) { std::shared_ptr<MyType> result( new MyType( functor ) ); result->m_me = result; return result; } private: FunctorHolder( TFunctor& functor ) : m_functor( functor ), m_me() { } TFunctor& m_functor; std::weak_ptr<MyType> m_me; template<class TFunctor, class ...TParams> friend class FunctorEventHandler; }; template<class TFunctor> std::shared_ptr<FunctorHolder<TFunctor>> createFunctorEventHandler( TFunctor&& functor ) { return FunctorHolder<TFunctor>::create( functor ); } } // handlers } // events #define FUNCTOR_HANDLER( Functor ) ::events::handlers::createFunctorEventHandler( Functor ) #define LAMBDA_HANDLER( Lambda ) FUNCTOR_HANDLER( Lambda ) #define STD_FUNCTION_HANDLER( StdFunction ) FUNCTOR_HANDLER( StdFunction ) #define FUNCTION_HANDLER( Function ) FUNCTOR_HANDLER( &Function ) 


./events/handlers/methodeventhandler.hpp
 #pragma once #include <memory> #include <assert.h> #include "abstracteventhandler.hpp" namespace events { namespace handlers { namespace { template<class TMethodHolder, class ...TParams> struct IsMethodParamsCompatible { private: template<class TCheckedMethodHolder, class ...TCheckedParams> static constexpr std::true_type exists( decltype( ( std::declval<TCheckedMethodHolder>().m_object.*std::declval<TCheckedMethodHolder>().m_method )( std::declval<TCheckedParams>()... ) )* = nullptr ) noexcept; template<class TCheckedMethodHolder, class ...TCheckedParams> static constexpr std::false_type exists( ... ) noexcept; public: static constexpr bool value = decltype( exists<TMethodHolder, TParams...>( nullptr ) )::value; }; } // template<class TMethodHolder, class ...TParams> class MethodEventHandler : public AbstractEventHandler<TParams...> { using MyType = MethodEventHandler<TMethodHolder, TParams...>; using TMethodHolderPtr = std::shared_ptr<TMethodHolder>; public: MethodEventHandler( TMethodHolderPtr methodHolder ) : AbstractEventHandler<TParams...>(), m_methodHolder( methodHolder ) { assert( m_methodHolder != nullptr ); } virtual void call( TParams... params ) override { static_assert( IsMethodParamsCompatible<TMethodHolder, TParams...>::value, "Event and method arguments are not compatible" ); ( m_methodHolder->m_object.*m_methodHolder->m_method )( params... ); } protected: virtual bool isEquals( const AbstractEventHandler<TParams...>& other ) const noexcept override { const MyType* _other = dynamic_cast<const MyType*>( &other ); return ( _other != nullptr && *m_methodHolder == *_other->m_methodHolder ); } private: TMethodHolderPtr m_methodHolder; }; template<class TObject, class TResult, class ...TParams> class MethodHolder { using MyType = MethodHolder<TObject, TResult, TParams...>; using TMethod = TResult( TObject::* )( TParams... ); public: template<class ...TCallParams> operator TEventHandlerPtr<TCallParams...>() { return TEventHandlerPtr<TCallParams...>( new MethodEventHandler<MyType, TCallParams...>( m_me.lock() ) ); } bool operator==( const MyType& other ) const noexcept { return ( &m_object == &other.m_object && m_method == other.m_method ); } bool operator!=( const MyType& other ) const noexcept { return !( *this == other ); } template<class TObject, class ...TParams> static std::shared_ptr<MyType> create( TObject& object, TMethod method ) { std::shared_ptr<MyType> result( new MyType( object, method ) ); result->m_me = result; return result; } private: MethodHolder( TObject& object, TMethod method ) : m_object( object ), m_method( method ) { assert( m_method != nullptr ); } TObject& m_object; TMethod m_method; std::weak_ptr<MyType> m_me; template<class TMethodHolder, class ...TParams> friend class MethodEventHandler; template<class TMethodHolder, class ...TParams> friend struct IsMethodParamsCompatible; }; template<class TObject, class TResult, class ...TParams> std::shared_ptr<MethodHolder<TObject, TResult, TParams...>> createMethodEventHandler( TObject& object, TResult( TObject::*method )( TParams... ) ) { return MethodHolder<TObject, TResult, TParams...>::create( object, method ); } } // handlers } // events #define METHOD_HANDLER( Object, Method ) ::events::handlers::createMethodEventHandler( Object, &Method ) #define MY_METHOD_HANDLER( Method ) METHOD_HANDLER( *this, Method ) 


./events/handlers/handlercast.hpp
 #pragma once #include <memory> #include "eventhandlerptr.h" namespace events { namespace handlers { template<class TSome> struct HandlerCast { template<class ...Types> static constexpr TEventHandlerPtr<Types...> cast( TSome& some ) { return static_cast<TEventHandlerPtr<Types...>>( some ); } }; template<class TPtr> struct HandlerCast<std::shared_ptr<TPtr>> { template<class ...Types> static constexpr TEventHandlerPtr<Types...> cast( std::shared_ptr<TPtr> some ) { return HandlerCast<TPtr>::cast<Types...>( *some ); } }; } // handlers } // events 


./events/event.hpp
 #pragma once #include <type_traits> #include <list> #include <memory> #include <shared_mutex> #include <algorithm> #include <assert.h> #include "handlers/abstracteventhandler.hpp" #include "handlers/eventhandlerptr.h" #include "handlers/handlercast.hpp" #include "joins/eventjoinwrapper.hpp" namespace events { namespace joins { template<class ...TParams> class HandlerEventJoin; } template<class ...TParams> class IEvent { public: template<class TSome> EventJoin operator+=( TSome&& some ) { EventJoin result( *this, std::forward<TSome>( some ) ); result.join(); return result; } template<class TSome> bool operator-=( TSome&& some ) { return removeHandler( handlers::HandlerCast<TSome>::cast<TParams...>( some ) ); } protected: using TMyEventHandlerPtr = handlers::TEventHandlerPtr<TParams...>; IEvent() {} virtual bool isHandlerAdded( const TMyEventHandlerPtr& eventHandler ) const = 0; virtual bool addHandler( TMyEventHandlerPtr eventHandler ) = 0; virtual bool removeHandler( TMyEventHandlerPtr eventHandler ) = 0; friend class joins::HandlerEventJoin<TParams...>; }; template<class ...TParams> class TEvent : public IEvent<TParams...> { using TEventHandlerIt = typename std::list<TMyEventHandlerPtr>::const_iterator; public: TEvent() : m_handlers(), m_currentIt(), m_isCurrentItRemoved( false ), m_handlerListMutex() { } void operator()( TParams... params ) { m_handlerListMutex.lock_shared(); m_isCurrentItRemoved = false; m_currentIt = m_handlers.begin(); while( m_currentIt != m_handlers.end() ) { m_handlerListMutex.unlock_shared(); ( *m_currentIt )->call( params... ); m_handlerListMutex.lock_shared(); if( m_isCurrentItRemoved ) { m_isCurrentItRemoved = false; TEventHandlerIt removedIt = m_currentIt; ++m_currentIt; deleteHandler( removedIt ); } else { ++m_currentIt; } } m_handlerListMutex.unlock_shared(); } protected: virtual bool isHandlerAdded( const TMyEventHandlerPtr& eventHandler ) const override { std::shared_lock<std::shared_mutex> _handlerListMutexLock( m_handlerListMutex ); return ( findEventHandler( eventHandler ) != m_handlers.end() ); } virtual bool addHandler( TMyEventHandlerPtr eventHandler ) override { std::unique_lock<std::shared_mutex> _handlerListMutexLock( m_handlerListMutex ); if( findEventHandler( eventHandler ) == m_handlers.end() ) { m_handlers.push_back( std::move( eventHandler ) ); return true; } return false; } virtual bool removeHandler( TMyEventHandlerPtr eventHandler ) override { std::unique_lock<std::shared_mutex> _handlerListMutexLock( m_handlerListMutex ); auto it = findEventHandler( eventHandler ); if( it != m_handlers.end() ) { if( it == m_currentIt ) m_isCurrentItRemoved = true; else deleteHandler( it ); return true; } return false; } private: //      'm_handlerListMutex' inline TEventHandlerIt findEventHandler( const TMyEventHandlerPtr& eventHandler ) const noexcept { return std::find_if( m_handlers.cbegin(), m_handlers.cend(), [ &eventHandler ]( const TMyEventHandlerPtr& oneHandler ) { return ( *oneHandler == *eventHandler ); } ); } //      'm_handlerListMutex' inline void deleteHandler( TEventHandlerIt it ) { m_handlers.erase( it ); } std::list<TMyEventHandlerPtr> m_handlers; //    'm_handlerListMutex' mutable TEventHandlerIt m_currentIt; mutable bool m_isCurrentItRemoved; mutable std::shared_mutex m_handlerListMutex; }; } // events 


./events/joins/abstracteventjoin.h
 #pragma once namespace events { namespace joins { class AbstractEventJoin { public: virtual ~AbstractEventJoin(); virtual bool isJoined() const = 0; virtual bool join() = 0; virtual bool unjoin() = 0; protected: AbstractEventJoin(); }; } // joins } // events 


./events/joins/abstracteventjoin.cpp
 #include "abstracteventjoin.h" namespace events { namespace joins { AbstractEventJoin::AbstractEventJoin() { } AbstractEventJoin::~AbstractEventJoin() { } } // joins } // events 


./events/joins/handlereventjoin.h
 #pragma once #include "abstracteventjoin.h" #include "../handlers/eventhandlerptr.h" namespace events { template<class ...TParams> class IEvent; namespace joins { template<class ...TParams> class HandlerEventJoin : public AbstractEventJoin { public: HandlerEventJoin( IEvent<TParams...>& _event, ::events::handlers::TEventHandlerPtr<TParams...> handler ) : AbstractEventJoin(), m_event( _event ), m_handler( handler ) { } virtual inline bool isJoined() const override; virtual inline bool join() override; virtual inline bool unjoin() override; private: IEvent<TParams...>& m_event; ::events::handlers::TEventHandlerPtr<TParams...> m_handler; }; } // joins } // events 


./events/joins/handlereventjoin.hpp
 #pragma once #include "handlereventjoin.h" #include "../event.hpp" namespace events { namespace joins { template<class ...TParams> bool HandlerEventJoin<TParams...>::isJoined() const { return m_event.isHandlerAdded( m_handler ); } template<class ...TParams> bool HandlerEventJoin<TParams...>::join() { return m_event.addHandler( m_handler ); } template<class ...TParams> bool HandlerEventJoin<TParams...>::unjoin() { return m_event.removeHandler( m_handler ); } } // joins } // events 


./events/joins/eventjoinwrapper.h
 #pragma once #include <memory> #include "../handlers/eventhandlerptr.h" namespace events { template<class ...TParams> class IEvent; namespace joins { class AbstractEventJoin; class EventJoinWrapper { public: template<class TSome, class ...TParams> inline EventJoinWrapper( IEvent<TParams...>& _event, TSome&& handler ); constexpr EventJoinWrapper() noexcept; EventJoinWrapper( EventJoinWrapper&& other ) noexcept; EventJoinWrapper( EventJoinWrapper& other ) noexcept; EventJoinWrapper& operator=( EventJoinWrapper&& other ) noexcept; EventJoinWrapper& operator=( const EventJoinWrapper& other ) noexcept; operator bool() const; bool isAssigned() const; bool isJoined() const; bool join(); bool unjoin(); private: std::shared_ptr<AbstractEventJoin> m_eventJoin; }; } // joins using EventJoin = joins::EventJoinWrapper; } // events 


./events/joins/eventjoinwrapper.hpp
 #pragma once #include "eventjoinwrapper.h" #include "handlereventjoin.h" #include "../handlers/handlercast.hpp" namespace events { namespace joins { template<class TSome, class ...TParams> EventJoinWrapper::EventJoinWrapper( IEvent<TParams...>& _event, TSome&& handler ) : m_eventJoin( std::make_shared<HandlerEventJoin<TParams...>>( _event, ::events::handlers::HandlerCast<TSome>::cast<TParams...>( handler ) ) ) { } } // joins } // events 


./events/joins/eventjoinwrapper.cpp
 #include "eventjoinwrapper.h" #include <type_traits> #include "abstracteventjoin.h" namespace events { namespace joins { constexpr EventJoinWrapper::EventJoinWrapper() noexcept : m_eventJoin( nullptr ) { } EventJoinWrapper::EventJoinWrapper( EventJoinWrapper&& other ) noexcept : m_eventJoin( std::move( other.m_eventJoin ) ) { } EventJoinWrapper::EventJoinWrapper( EventJoinWrapper& other ) noexcept : m_eventJoin( other.m_eventJoin ) { } EventJoinWrapper& EventJoinWrapper::operator=( EventJoinWrapper&& other ) noexcept { m_eventJoin = std::move( other.m_eventJoin ); return *this; } EventJoinWrapper& EventJoinWrapper::operator=( const EventJoinWrapper& other ) noexcept { m_eventJoin = other.m_eventJoin; return *this; } EventJoinWrapper::operator bool() const { return isJoined(); } bool EventJoinWrapper::isAssigned() const { return ( m_eventJoin != nullptr ); } bool EventJoinWrapper::isJoined() const { return ( m_eventJoin != nullptr && m_eventJoin->isJoined() ); } bool EventJoinWrapper::join() { return ( m_eventJoin != nullptr ? m_eventJoin->join() : false ); } bool EventJoinWrapper::unjoin() { return ( m_eventJoin != nullptr ? m_eventJoin->unjoin() : false ); } } // joins } // events 



UPD1. Aquí y anteriormente en el artículo, se proporciona el código escrito bajo VC ++ 14 . Por compatibilidad con otros compiladores, es mejor tomar el código del enlace. Un agradecimiento especial a Cheater por proporcionar compatibilidad con el GCC .
UPD2. Gracias lexxmark por notar un agujero de seguridad de hilo en términos de múltiples llamadas de eventos simultáneos.
Pequeñas mejoras
 namespace { template<class ...TParams> struct TypeHelper { using TEventHandlerPtr = handlers::TEventHandlerPtr<TParams...>; using TEventHandlerIt = typename std::list<TEventHandlerPtr>::const_iterator; }; } // template<class ...TParams> class IEvent { . . . protected: using TMyEventHandlerPtr = typename TypeHelper<TParams...>::TEventHandlerPtr; . . . }; namespace { template<class ...TParams> struct EventCore { using TMyHandlerPtr = typename TypeHelper<TParams...>::TEventHandlerPtr; std::list<TMyHandlerPtr> handlers; mutable std::shared_mutex coreMutex; }; template<class ...TParams> class HandlerRunner { using TMyEventCore = EventCore<TParams...>; using TMyHandlerIt = typename TypeHelper<TParams...>::TEventHandlerIt; public: HandlerRunner( TMyEventCore& eventCore ) : m_eventCore( eventCore ), currentIt(), wasRemoving( false ) { } void run( TParams... params ) { m_eventCore.coreMutex.lock_shared(); currentIt = m_eventCore.handlers.begin(); wasRemoving = false; while( currentIt != m_eventCore.handlers.end() ) { m_eventCore.coreMutex.unlock_shared(); ( *currentIt )->call( params... ); m_eventCore.coreMutex.lock_shared(); if( wasRemoving ) wasRemoving = false; else ++currentIt; } m_eventCore.coreMutex.unlock_shared(); } TMyHandlerIt currentIt; mutable bool wasRemoving; private: TMyEventCore& m_eventCore; }; } // template<class ...TParams> class TEvent : public IEvent<TParams...> { using TMyEventHandlerPtr = typename TypeHelper<TParams...>::TEventHandlerPtr; using TMyEventHandlerIt = typename TypeHelper<TParams...>::TEventHandlerIt; using TMyHandlerRunner = HandlerRunner<TParams...>; public: TEvent() : m_core() { } void operator()( TParams... params ) { TMyHandlerRunner newHandlerRunner( m_core ); m_core.coreMutex.lock_shared(); auto it = m_handlerRunners.insert( m_handlerRunners.end(), &newHandlerRunner ); m_core.coreMutex.unlock_shared(); newHandlerRunner.run( params... ); m_core.coreMutex.lock_shared(); m_handlerRunners.erase( it ); m_core.coreMutex.unlock_shared(); } protected: virtual bool isHandlerAdded( const TMyEventHandlerPtr& eventHandler ) const override { std::shared_lock<std::shared_mutex> _coreMutexLock( m_core.coreMutex ); return ( findEventHandler( eventHandler ) != m_core.handlers.end() ); } virtual bool addHandler( TMyEventHandlerPtr eventHandler ) override { std::unique_lock<std::shared_mutex> _coreMutexLock( m_core.coreMutex ); if( findEventHandler( eventHandler ) == m_core.handlers.end() ) { m_core.handlers.push_back( std::move( eventHandler ) ); return true; } return false; } virtual bool removeHandler( TMyEventHandlerPtr eventHandler ) override { std::unique_lock<std::shared_mutex> _coreMutexLock( m_core.coreMutex ); auto it = findEventHandler( eventHandler ); if( it != m_core.handlers.end() ) { for( TMyHandlerRunner* oneHandlerRunner : m_handlerRunners ) { if( it == oneHandlerRunner->currentIt ) { ++oneHandlerRunner->currentIt; oneHandlerRunner->wasRemoving = true; } } m_core.handlers.erase( it ); return true; } return false; } private: //      'm_core.coreMutex' inline TMyEventHandlerIt findEventHandler( const TMyEventHandlerPtr& eventHandler ) const { return std::find_if( m_core.handlers.cbegin(), m_core.handlers.cend(), [ &eventHandler ]( const TMyEventHandlerPtr& oneHandler ) { return ( *oneHandler == *eventHandler ); } ); } EventCore<TParams...> m_core; std::list<TMyHandlerRunner*> m_handlerRunners; }; 

(, , ) HandlerRunner , . , : currentIt ( ) wasRemoving (, ). HandlerRunner' operator() ; (, ) , EventCore . T.O. , , , , , .
UPD3. Gracias a isnullxbh , se encontró otro error. Está asociado con el almacenamiento inadecuado y el acceso posterior a los objetos pasados ​​por rvalue (principalmente expresiones lambda).
Corrección
, lvalue , lvalue -, , rvalue , (, ). :
 template<class TSome> struct ObjectSaver; template<class LValue> struct ObjectSaver<LValue&> { using TObject = LValue&; }; template<class RValue> struct ObjectSaver<RValue&&> { using TObject = RValue; }; 

Holder ( lvalue rvalue ), , «» . type erasing ( ). , Holder' .
 template<class TBase> struct AbstractInnerHolder { virtual ~AbstractInnerHolder() {} virtual inline TBase& get() = 0; inline const TBase& get() const { return const_cast<AbstractInnerHolder<TBase>&>( *this ).get(); } }; template<class TBase, class TInner> struct TInnerHolder : public AbstractInnerHolder<TBase> { using TInnerObject = typename ObjectSaver<TInner>::TObject; TInnerHolder( TInner _inner ) : AbstractInnerHolder<TBase>(), inner( std::forward<TInner>( _inner ) ) { } virtual inline TBase& get() override { return static_cast<TBase&>( inner ); } TInnerObject inner; }; template<class TAssignBase, class TArgInner> AbstractInnerHolder<TAssignBase>& createInnerHolder( TArgInner&& inner ) { using TAssignInner = decltype( inner ); return *new TInnerHolder<TAssignBase, TAssignInner>( std::forward<TArgInner>( inner ) ); } 

Holder' . MethodHolder' .
 template<class TObject, class TResult, class ...TParams> class MethodHolder { . . . using MyType = MethodHolder<TObject, TResult, TParams...>; using TMethod = TResult( TObject::* )( TParams... ); public: ~MethodHolder() { delete &m_innerHolder; } bool operator==( const MyType& other ) const { return ( &m_innerHolder.get() == &other.m_innerHolder.get() && m_method == other.m_method ); } template<class TArgObject> static std::shared_ptr<MyType> create( TArgObject&& object, TMethod method ) { std::shared_ptr<MyType> result( new MyType( std::forward<TArgObject>( object ), method ) ); result->m_me = result; return result; } private: template<class TArgObject> MethodHolder( TArgObject&& object, TMethod method ) : m_innerHolder( createInnerHolder<TObject>( std::forward<TArgObject>( object ) ) ), m_method( method ) { assert( m_method != nullptr ); } AbstractInnerHolder<TObject>& m_innerHolder; TMethod m_method; std::weak_ptr<MyType> m_me; . . . }; 

 namespace { template<class TMethodHolder, class ...TParams> struct IsMethodParamsCompatible { private: template<class TCheckedMethodHolder, class ...TCheckedParams> static constexpr std::true_type exists( decltype( ( std::declval<TCheckedMethodHolder>().m_innerHolder.get().*std::declval<TCheckedMethodHolder>().m_method )( std::declval<TCheckedParams>()... ) )* = nullptr ); . . . }; } // template<class TMethodHolder, class ...TParams> class MethodEventHandler : public AbstractEventHandler<TParams...> { public: virtual void call( TParams... params ) override { static_assert( IsMethodParamsCompatible<TMethodHolder, TParams...>::value, "Event and method arguments are not compatible" ); ( m_methodHolder->m_innerHolder.get().*m_methodHolder->m_method )( params... ); } private: TMethodHolderPtr m_methodHolder; . . . }; 

 template<class TObject, class TResult, class ...TParams> std::shared_ptr<MethodHolder<typename std::decay<TObject>::type, TResult, TParams...>> createMethodEventHandler( TObject&& object, TResult( std::decay<TObject>::type::*method )( TParams... ) ) { return MethodHolder<std::decay<TObject>::type, TResult, TParams...>::create( std::forward<TObject>( object ), method ); } 

Listo FunctorHolder . . - .

Comparación de PS con el mecanismo de señal / ranura Qt


Creo que no me equivocaré si digo que Qt es un marco muy común para el desarrollo en C ++ . Entre otras cosas, también tiene su propio mecanismo de procesamiento de eventos , en el que hay señales como análogos de eventos y ranuras como análogos de controladores. Se implementa utilizando el compilador Meta-Object , que forma parte del sistema Meta-Object más global , que, a su vez, se implementa utilizando el complemento utilizado en Qt sobre C ++ .

Características de ambas implementaciones:


  • la capacidad de conectar señales (eventos) a métodos (funciones miembro), functores y funciones;
  • () (), ( lvalue , rvalue );
  • ( );
  • () ( ).

Qt :



Qt :


  • QObject ;
    , , QObject , , , ( : Virtual inheritance with QObject is not supported. ); , , ;
  • template';
    , public - QObject ; moc' ; ,
    ,
     #include <QObject> class AbstractProperty : public QObject { Q_OBJECT protected: AbstractProperty(); signals: void valueChanged(); }; template<class TSource> class TProperty : public AbstractProperty { public: TProperty( const TSource& value = TSource() ) : AbstractProperty(), m_value( value ) { } const TSource& value() const { return m_value; } void setValue( const TSource& newValue ) { if( newValue != m_value ) { m_value = newValue; emit valueChanged(); } } private: TSource m_value; }; 

    , valueChanged ( , ) , .
    , , ;
  • .cpp-;
    ;
  • QMetaObject::Connection ;
    , Qt ( ) , ; () , , ; , ; Qt ;
  • uso de código adicional generado por moc ;
    esto ya es completamente subjetivo, pero la decisión es, donde para cada clase que usa señales y ranuras (las ranuras no siempre) hay varios archivos generados (por archivo para cada configuración) que causan algunos inconvenientes; pero para ser honesto, este es el defecto más pequeño.

Es importante tener en cuenta que esta comparación con Qt es, sin embargo, muy subjetiva y no tiene como objetivo exaltar o condenar este marco. Debe recordarse que, además del mecanismo de señal / ranura, Qt proporciona una gran funcionalidad, tanto utilizando este mecanismo como sin depender de él. En cualquier caso, siempre depende de usted decidir qué usar y qué no.

Source: https://habr.com/ru/post/es424593/


All Articles