C ++ 11 und Ereignisbehandlung

Ich denke, dass die Ereignisverarbeitung als Interaktionsmethode zwischen Objekten in OOP fast jedem bekannt ist, der OOP mindestens einmal berührt hat. Zumindest ist dieser Ansatz in einem meiner Meinung nach sehr breiten Aufgabenbereich sehr praktisch. In vielen Programmiersprachen ist die Event-Handling-Engine integriert. In C ++ gibt es jedoch keinen solchen Mechanismus. Mal sehen, was Sie dagegen tun können.

Kurze Einführung


Ein Ereignis kann unter bestimmten Bedingungen mit einem Objekt geschehen (z. B. mit einer Schaltfläche, wenn Sie mit der Maus darauf klicken). Andere Unternehmen müssen sich dessen möglicherweise bewusst sein. dann abonnieren sie die Veranstaltung . In diesem Fall wird beim Eintreten eines Ereignisses der Handler eines Objekts eines Drittanbieters aufgerufen, das das Ereignis abonniert hat. Somit hat er die Möglichkeit, Code auszuführen, d.h. auf ein Ereignis reagieren. Ebenso kann ein Objekt ein Ereignis abbestellen, wenn es nicht mehr darauf reagieren möchte. Infolgedessen haben wir viele Objekte, die mithilfe der Ereignisse eines von ihnen und der Reaktion auf diese Ereignisse anderer miteinander verbunden werden können.

So etwas, obwohl das jeder weiß.

Einfachste Implementierung


Es scheint einfach, ein solches Verhalten zu implementieren. Und es könnte so aussehen:

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 ) 

Die Anwendung dieses Falles sollte folgende Form haben:

 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 ); . . . } 

Natürlich wird eine Handler-Methode (-Funktionsmitglied einer Klasse) nicht die einzige Art von Handlern sein, aber dazu später mehr.

Alles scheint bequem, kompakt und großartig zu sein. Es gibt jedoch eine Reihe von Mängeln.

Handler-Vergleich


Um das Abbestellen eines Ereignisses zu implementieren, muss die Vergleichsmöglichkeit zum Handler hinzugefügt werden (by == und ! == ). Diejenigen Handler, die dieselbe Methode (eine Elementfunktion einer Klasse) desselben Objekts (d. H. Dieselbe Instanz derselben Klasse) aufrufen, werden als gleich betrachtet.

 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; . . . }; 

Dann können wir Handler aus dem Ereignisabonnement entfernen. In diesem Fall muss das Hinzufügen derselben (gleichen) Handler verboten werden.

 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; . . . }; 

Hier geben die Funktionen zum Hinzufügen / Entfernen des Handlers bei Erfolg true und false zurück, wenn die entsprechende Aktion (Hinzufügen oder Entfernen) nicht ausgeführt wurde.

Ja, der Anwendungsfall mit Vergleich beinhaltet die Erstellung temporärer, nirgends hinzugefügter Handler, die nirgendwo gelöscht werden. Aber dazu später mehr.

Kann das verwendet werden? Noch nicht vollständig implementiert.

Entfernen eines Handlers in einem Handler


Wir stoßen also sofort auf einen Absturz während der Codeausführung, bei dem sich der Handler vom Ereignis abmeldet (ich denke, es ist nicht der seltenste Anwendungsfall, wenn der Handler unter keinen Umständen selbst schneidet):

 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 ); . . . } 

Das Problem tritt aus einem sehr einfachen Grund auf:

  • Das Ereignis wird ausgelöst und beginnt mit der Iteration (unter Verwendung von Iteratoren) von Handlern, wobei diese aufgerufen werden.
  • der nächste Handler in sich bewirkt, dass er selbst gelöscht wird;
  • Das Ereignis löscht den angegebenen Handler und macht den entsprechenden Iterator ungültig.
  • Nach Abschluss dieses Handlers kehrt das Ereignis zur Aufzählung der anderen zurück. Der aktuelle Iterator (der dem Remote-Handler entspricht) ist jedoch bereits ungültig.
  • Das Ereignis versucht, auf den ungültigen Iterator zuzugreifen, was zu einem Absturz führt.

Daher müssen Fälle überprüft werden, in denen die Liste der Handler geändert werden kann, was zur Ungültigmachung von Iteratoren führen würde. und dann einen Leseschutz für solche Iteratoren implementieren.

Der Vorteil von std :: list 'in dieser Anwendung ist die Tatsache, dass beim Löschen nur ein Iterator ungültig wird - für das gelöschte Element (was beispielsweise Folgendes betrifft); Das Hinzufügen eines Elements führt überhaupt nicht zur Ungültigmachung von Iteratoren. Daher müssen wir den einzigen Fall steuern: Löschen eines Elements, dessen Iterator in der aktuellen Aufzählung der Elemente aktuell ist. In diesem Fall können Sie beispielsweise ein Element nicht löschen, sondern einfach markieren, dass das aktuelle Element gelöscht werden soll, und es innerhalb der Aufzählung von Elementen ausführen lassen.

Es wäre möglich, die Implementierung sofort einzuführen, aber ich schlage vor, dieses Problem zusammen mit den folgenden zu lösen.

Gewindesicherheit


Möglicherweise sind Aufrufe von drei möglichen Funktionen - Hinzufügen, Löschen und Sortieren von Handlern (wenn ein Ereignis ausgelöst wird) - von verschiedenen Threads zu zufälligen Zeiten möglich. Dies schafft ein ganzes Feld von Möglichkeiten für ihre "zeitliche Überschneidung", "Überlappung" ihrer Ausführung untereinander und den daraus resultierenden Rückgang des Programms. Versuchen wir dies zu vermeiden. Mutexe sind unser Alles .

 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; }; 

Vergessen Sie nicht, das Fenster fast jedes Mal offen zu lassen, wenn Sie jeden Handler anrufen. Dies ist erforderlich, damit Sie im Handler auf das Ereignis zugreifen und es ändern können (z. B. Handler hinzufügen / entfernen), ohne einen Deadlock zu verursachen. Sie können keine Angst vor der Gültigkeit der Daten haben, denn wie wir herausgefunden haben, führt das einzige, was dazu führt, das Löschen des aktuellen Elements, und diese Situation wurde verarbeitet.
UPD1. Danke Cheater , schlug vor, dass std :: shared_mutex nur in C ++ 17 erscheint (und std :: shared_lock nur in C ++ 14 ). Diejenigen, für die dies kritisch ist, werden wahrscheinlich mit std :: mutex zu tun haben.
UPD2. Weiter über die Thread-Sicherheit (ohne die Reihenfolge der Erzählungen beizubehalten).

Problem mit der Sichtbarkeit von Ereignissen


Wenn Sie ein Ereignis als Mitglied einer Klasse verwenden, erscheint es logisch, es öffentlich zu machen, damit Objekte von Drittanbietern ihre Handler hinzufügen / entfernen können. Dies führt jedoch zu operator () , d.h. Ein Ereignisanruf ist auch von außen zugänglich, was in einigen Fällen nicht akzeptabel sein kann. Wir werden dieses Problem lösen, indem wir aus der Ereignisklasse ( TEvent <...> ) eine abstrakte Schnittstelle isolieren, die nur für die Behandlung von Handlern vorgesehen ist.

 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-=' } . . . }; 

Jetzt können wir den Teil des Ereignisses, der für die Arbeit mit Handlern verantwortlich ist, und den Teil, der für den Aufruf verantwortlich ist, in verschiedene Bereiche aufteilen.

 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; . . . }; 

Somit können Objekte von Drittanbietern ihre Handler jetzt über TestWindow :: onButtonClick hinzufügen / entfernen, können dieses Ereignis jedoch nicht selbst auslösen. Ein Aufruf kann jetzt nur innerhalb der TestWindow- Klasse (und ihrer Nachkommen, wenn beispielsweise der Umfang des Ereignisses geschützt ist ) ausgeführt werden.

Der triviale Code verwandelt sich langsam in etwas Ungeheuerliches, aber dies ist nicht das Ende.

Passen Sie die Ereignisparameter und ihre Handler an


In der aktuellen Implementierung müssen das Ereignis und alle seine Handler über eine streng entsprechende Liste von Parametern verfügen. Dies führt zu einer Reihe von Nachteilen.

Der erste. Angenommen, wir haben eine Klassenvorlage, in der sich ein Ereignis mit einem Vorlagenparameter befindet.

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

Aufgrund der Tatsache, dass der Typ, der hier verwendet wird, nicht im Voraus bekannt ist, ist es sinnvoll, ihn als konstante Verknüpfung und nicht als Wert zu übergeben. Für jede Implementierung müssen jedoch auch bei grundlegenden Typen entsprechende Handler vorhanden sein.

 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 

Ich möchte Handler der Form MyHandlerClass :: handleValueChanged2 mit einem ähnlichen Ereignis verbinden können, aber bisher gibt es keine solche Möglichkeit.

Der zweite. Versuchen wir, einen Funktor-Handler zu implementieren, der einer vorhandenen Handler-Methode (-Funktionsmitglied einer Klasse) ähnelt.

 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 ) 

Versuchen Sie nun, es auf ein Ereignis zu schrauben.

 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 ); . . . } 

Das Ergebnis ist ein Kompilierungsfehler. Für die Funktion createFunctorEventHandler kann der Compiler die TParam- Typen nicht aus dem einzigen Argument für diese Funktion ableiten - dem Funktor selbst. Der Funktor enthält wirklich keine Informationen darüber, welche Art von Handler basierend darauf erstellt werden soll. Das einzige, was in dieser Situation getan werden kann, ist etwas zu schreiben wie:

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

Aber das wollen Sie gar nicht.

Verbinden eines Ereignisses mit verschiedenen Arten von Handlern


Es gibt also eine Wunschliste, die der Implementierung überlassen bleibt. Wir werden die Situation am Beispiel eines Funktor-Handlers betrachten, eine Handler-Methode (-Funktionsmitglied einer Klasse) wird auf ähnliche Weise erhalten.

Da es allein aufgrund eines Funktors unmöglich ist zu sagen, wie die Liste der Parameter des entsprechenden Handlers aussehen wird, werden wir dies nicht tun. Diese Frage wird nicht zum Zeitpunkt der Erstellung des Handlers relevant, sondern zum Zeitpunkt des Versuchs, sie einem bestimmten Ereignis zuzuordnen. Und ja, das sind zwei verschiedene Punkte. Diese Idee kann wie folgt umgesetzt werden:

 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; }; 

Kurz gesagt, die Trennung der Momente der Erstellung des Handlers und seiner Bindung an das Ereignis ist hier ausgeprägter als zuvor. Dies umgeht die im vorherigen Absatz beschriebenen Probleme. Typkompatibilitätstests werden durchgeführt, wenn versucht wird, einen bestimmten FunctorHolder an einen bestimmten FunctorEventHandler anzuhängen oder vielmehr eine Instanz der FunctorEventHandler <...> -Klasse mit einem ganz bestimmten Funktortyp zu erstellen. und in dieser Klasse gibt es eine Codezeile m_functorHolder.m_functor (params ...); , die einfach nicht für eine Reihe von Typen kompiliert werden können, die mit einem Funktor nicht kompatibel sind (oder wenn es überhaupt kein Funktor ist, d. h. ein Objekt, das keinen Operator () hat ).

Ich wiederhole, dass das Problem des Löschens temporärer Objekte unten diskutiert wird. Darüber hinaus ist anzumerken, dass für jeden Fall eine Reihe von Makros erstellt wurden, um zum einen die Fähigkeiten dieses Handlertyps zu demonstrieren und zum anderen, falls einer von ihnen mit einer Datei geändert werden sollte.

Überprüfen Sie das Ergebnis.

 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 . . . } 

Beim Versuch, einen der Parameter von const lvalue in lvalue zu konvertieren, tritt ein Kompilierungsfehler auf. Das Konvertieren von rWert in unkonstanten lWert verursacht keinen Fehler, obwohl es erwähnenswert ist, dass eine potenzielle Gefahr eines Selbstschusses im Bein besteht: Der Handler kann die auf den Stapel kopierte Variable ändern, die beim Beenden dieses Handlers freudig gelöscht wird.

Im Allgemeinen sollte die Fehlermeldung ungefähr so ​​aussehen:

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

Zur besseren Übersichtlichkeit können Sie bei Verwendung von Ereignissen und Handlern im Code von Drittanbietern Ihre eigene Fehlermeldung hinzufügen. Dazu muss eine kleine unterstützende Struktur geschrieben werden (ich gebe zu, ich habe irgendwo einen ähnlichen Ansatz ausspioniert):

 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... ); } . . . }; 

Diese Arbeit basiert auf dem SFINAE- Mechanismus. Kurz gesagt, es wird versucht, die erste Funktion zu kompilieren. Wenn dies jedoch aufgrund der Inkompatibilität der Argumente (oder des Fehlens von operator () aus dem, was als Funktor übergeben wurde) nicht funktioniert, gibt der Compiler keinen Fehler aus, sondern versucht lediglich, die zweite Funktion zu kompilieren. Wir tun alles so, dass die Kompilierung immer erfolgreich ist, und schließen dann, nachdem die Funktion kompiliert wurde, (indem wir das Ergebnis in den Wert schreiben) über die Kompatibilität der Argumente für die angegebenen Typen.

Jetzt sieht die Fehlermeldung ungefähr so ​​aus:

 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 &' 

Zusätzlich zu einer zusätzlichen, informativeren Fehlermeldung löst dieser Ansatz das Problem der Konvertierung von Argumenten von rWert in unkonstanten Wert : Jetzt verursacht er einen Argumentinkompatibilitätsfehler, d. H. Der Versuch , den functor6- Handler aus dem obigen Beispiel hinzuzufügen, führt zu einem Fehler bei der Kompilierung.
UPD Verfeinerung (ohne die narrative Reihenfolge beizubehalten ).

Funktionsvergleich


Aufgrund von Änderungen in der Handlerklasse ändert sich die Implementierung des Vergleichens von Instanzen dieser Klasse geringfügig. Ich werde noch einmal eine Implementierung nur eines Funktor-Handlers bereitstellen, da die Handler-Methode (-Funktionsmitglied der Klasse) ähnlich aussehen wird.

 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; . . . }; 

Hierbei enden die Ähnlichkeiten bei der Durchführung des Vergleichs und der Teil beginnt nur für die Handler-Funktoren.

Wie oben erwähnt, haben wir verschiedene Arten von Funktorhandlern erhalten: direkte Funktorobjekte, Lambda-Ausdrücke, Instanzen der Klasse std :: function , einzelne Funktionen. Von diesen können Funktorobjekte, Lambda-Ausdrücke und Instanzen der Klasse std :: function nicht mit operator == verglichen werden (sie müssen an der Adresse verglichen werden), einzelne Funktionen jedoch, weil bereits gespeichert bei. Um die Vergleichsfunktion nicht für jeden Fall separat umzuschreiben, schreiben wir sie in allgemeiner Form:

 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; . . . }; 

Es versteht sich, dass is_equatable eine Hilfsvorlage ist, die bestimmt, ob zwei Instanzen eines bestimmten Typs auf Gleichheit überprüft werden können. Mit seiner Hilfe wählen wir mit std :: enable_if eine von zwei teilweise spezialisierten EqualityChecker- Strukturen aus , die einen Vergleich durchführen: nach Wert oder nach Adresse. Is_equatable implementiert ist , kann wie folgt sein:

 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; }; 

Diese Implementierung basiert auf dem Mechanismus SFINAE , die bereits verwendet wurden zuvor . Nur hier überprüfen wir das Vorhandensein von operator == für Instanzen einer bestimmten Klasse.

Auf diese einfache Weise ist die Implementierung des Vergleichs von Handler-Funktoren fertig.

Müllabfuhr


Seien Sie nachsichtig, ich wollte auch eine laute Überschrift einfügen.

Wir nähern uns dem Finale und es ist Zeit, die große Anzahl von erstellten Objekten loszuwerden, die niemand kontrolliert.

Jede Ereignisaktion mit einem Handler erstellt zwei Objekte: Holder , in dem der ausführbare Teil des Handlers gespeichert ist , und EventHandlerVerbinden Sie es mit dem Ereignis. Wir werden nicht vergessen, dass im Falle eines erneuten Hinzufügens des Handlers keine Hinzufügung erfolgt - zwei Objekte werden „in der Luft aufgehängt“ (es sei denn, dieser Fall wird natürlich jedes Mal separat geprüft). Eine andere Situation: Entfernen des Handlers; Es werden auch zwei neue Objekte erstellt, um in der Liste der Ereignishandler nach demselben (Gleichen) zu suchen. Der gefundene Handler aus der Liste wird natürlich gelöscht (falls vorhanden), und dieser temporäre Handler, der für die Suche erstellt wurde und aus zwei Objekten besteht, befindet sich wieder in der Luft. Im Allgemeinen nicht cool.

Wenden Sie sich an intelligente Zeiger . Wir müssen bestimmen, wie die Semantik des Eigentums an jedem der beiden Handlerobjekte lautet : Alleineigentum ( std :: unique_ptr ) oder Shared ( std :: shared_ptr ).

InhaberNeben der Verwendung durch das Ereignis selbst beim Hinzufügen / Entfernen sollte es im EventHandler gespeichert werden. Daher verwenden wir es für das gemeinsame Eigentum und für den EventHandler ist es allein, weil Nach der Erstellung wird es nur in der Liste der Ereignishandler gespeichert.

Wir realisieren diese Idee:

 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 ) 

Das Wichtigste zuerst.

Zunächst das Ereignis und seine Schnittstelle für die Arbeit mit Handlern. In letzterem Fall können Sie Typen nicht mehr direkt mit static_cast konvertieren , da der zu konvertierende Typ "innerhalb" von std :: shared_ptr liegt . Für eine solche Konvertierung verwenden wir jetzt die zusätzliche HandlerCast- Struktur , die durch ihre private Spezialisierung den Zugriff auf das Objekt in std :: shared_ptr ermöglicht und bereits mit ihr arbeitet (in ihrer nicht spezialisierten Implementierung) und den guten alten static_cast anwendet .

Das Ereignis selbst; Auch hier gibt es einige wichtige Änderungen. Zunächst beenden wir das manuelle Löschen von Handlerinstanzen im Destruktor und beim Löschen. Jetzt reicht es aus, den Smart Pointer mit diesem Handler aus der Liste zu entfernen. Außerdem ist es beim Hinzufügen eines Handlers wichtig, std :: move nicht zu vergessen , da std :: unique_ptr unterstützt das Kopieren nicht (was für eine solche Semantik durchaus logisch ist).

Gehen wir weiter zu den Handlern. Nach der alten Tradition ist nur einer von ihnen gegeben, der zweite ist ähnlich. Und hier kommt es auf den ersten Blick darauf an, die Arten gespeicherter / erstellter Objekte von Links / Zeigern zu intelligenten Zeigern zu ändern.

Aber es gibt einen subtilen Punkt. Die Funktion createMethodEventHandler gibt std :: shared_ptr an eine Instanz zurückMethodHolder . Etwas später wird versucht, es in einen Handlertyp ( MethodEventHandler ) zu konvertieren , in dem eine neue Instanz von MethodEventHandler erstellt und an den Konstruktor std :: shared_ptr übergeben werden muss. Genau dies war beabsichtigt, damit die MethodHolder- Instanz später gelöscht wird, wenn die MethodEventHandler- Instanz gelöscht wurde . Das Problem ist jedoch, dass MethodHolder keinen Zugriff auf das bereits erstellte std :: shared_ptr hat , in dem es selbst gespeichert ist .

Um das Problem zu lösen, müssen Sie in MethodHolder einen intelligenten Zeiger auf sich selbst speichern . Wir verwenden jedoch, damit die Entfernung nicht beeinträchtigt wirdstd :: schwach_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 ); } 

Aus Gründen der Übersichtlichkeit gebe ich beim Entfernen eines Handlers aus einem Ereignis eine ungefähre Reihenfolge der Ereignisse an (ich entschuldige mich für ein zufälliges Wortspiel):

  • Das Ereignis entfernt das Element aus der Liste ( m_handlers.erase (it); ), wodurch der Destruktor aufgerufen wird.
  • Der Destruktor std :: unique_ptr wird aufgerufen , was zu einem Aufruf des Destruktors des verwalteten Objekts führt.
  • Der Destruktor MethodEventHandler wird aufgerufen , der alle Felder des Objekts löscht, einschließlich des Felds m_methodHolder , das std :: shared_ptr lautet .
  • std::shared_ptr ; , (.. ) ( MethodHolder ); , std::weak_ptr ;
  • MethodHolder , , , m_me , std::weak_ptr ;
  • std::weak_ptr ; ; weil std::weak_ptr , ;
  • Gewinn.

Es ist wichtig zu beachten, dass der Destruktor der AbstractEventHandler- Klasse virtuell sein muss. Andernfalls wird nach Klausel 2 in Klausel 3 der AbstractEventHandler- Destruktor aufgerufen und weitere Aktionen werden nicht ausgeführt.

Ereignis- und Handlerverbindung


In einigen Fällen, wenn das Hinzufügen / Entfernen eines Handlers zu einem Ereignis ziemlich häufig vorkommt (gemäß einer Logik), möchten Sie nicht herumspielen und jedes Mal eine Instanz des Ereignisses und eine Instanz des Handlers abrufen, um erneut ein Abonnement / Abmelden von diesem Ereignis zu implementieren. Aber ich möchte sie einmal verbinden und dann, falls erforderlich, mit dieser Verbindung arbeiten und einen vordefinierten Handler zu einem vordefinierten Ereignis hinzufügen / daraus entfernen. Sie können dies wie folgt implementieren:

 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; }; 

Wie Sie sehen können, wurde jetzt ein weiterer möglicher Ort zum Speichern der Instanz des Handlers hinzugefügt, sodass wir hierfür std :: shared_ptr anstelle von std :: unique_ptr verwenden .

Diese Klasse ist für mich jedoch etwas unpraktisch. Ich möchte Verbindungsinstanzen ohne eine Liste von Parametern speichern und erstellen, die die Klassenvorlage instanziieren.

Wir implementieren dies mithilfe einer abstrakten Ahnenklasse und eines Wrappers:

 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 ist dieselbe unterstützende Struktur, die hier verwendet wurde . Übrigens ist es wichtig, nicht zu vergessen, den AbstractEventJoin- Destruktor virtuell zu machen, damit beim Löschen seiner Instanz im EventJoinWrapper- Destruktor der HandlerEventJoin- Destruktor aufgerufen wird , da sonst das Feld THandlerPtr und damit der Handler selbst nicht zerstört werden .

Diese Implementierung scheint praktikabel zu sein, aber nur auf den ersten Blick. Durch Kopieren oder Verschieben einer Instanz von EventJoinWrapper wird m_eventJoin in seinem Destruktor erneut gelöscht . Daher verwenden wir std :: shared_ptr , um die Instanz zu speichernAbstractEventJoin sowie eine leicht optimierte Semantik der Bewegung (und des Kopierens) implementieren, weil Dies ist eine möglicherweise häufige Operation.

 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; }; 

Wenn Sie jetzt einen Handler mit einem Ereignis verbinden, können Sie sofort eine Instanz einer neuen Verbindung zurückgeben:

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

Und nachdem Sie die dreieckige Abhängigkeit durch include aufgelöst haben (IEvent <= EventJointWrapper.hpp; EventJointWrapper <= HandlerEventJoin.hpp; HandlerEventJoin <= IEvent.hpp), indem Sie einige Dateien in .h und .hpp unterteilen , können Sie sogar damit arbeiten.

Das Erstellen von Verbindungsinstanzen erfolgt nach denselben Regeln wie beim Abonnieren des Ereignishandlers:

 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; } 

Außerdem können Sie die Ereignisverarbeitung "aktivieren" / "deaktivieren" (für die im Prinzip Verbindungen erstellt wurden):

 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. 

Zusammenfassung


Zunächst ist anzumerken, dass die Aufgabe, einen Artikel so kurz wie möglich und kurz und bündig zu schreiben, völlig gescheitert ist.

Ich hoffe, dass die daraus resultierende Implementierung der Ereignisverarbeitung sehr funktional ist und zumindest jemandem nützlich sein wird.

Ein sehr umständliches Beispiel, das die Hauptmerkmale demonstriert
 #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; } 

Fazit:

 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: 


Es ist erwähnenswert, einige wichtige Punkte zu beachten:

  • Es wurde nicht separat angegeben, daher möchte ich erwähnen, dass das Ereignis selbst in dieser Implementierung ein Funktor ist. Dies bedeutet, dass es als Handler für ein anderes Ereignis fungieren kann.
  • Jetzt können Sie keine konstanten Methoden (Klassenelementfunktionen) als Handler verwenden. Ich denke, wenn eine solche Gelegenheit benötigt wird, ist es nicht schwierig, einen neuen Typ von Handler dafür zu schreiben, der auf bestehenden basiert.

Darüber hinaus wurden in der endgültigen Version einige Punkte im Artikel weggelassen, um die Sichtbarkeit und Lesbarkeit des Codes zu verbessern:

  • Der Typ des Rückgabewerts der Methode (Elementfunktion der Klasse) für den entsprechenden Handler kann beliebig sein, nicht unbedingt ungültig (für Handler-Funktoren wurde dies ebenfalls durchgeführt).
  • Die gesamte Implementierung ist in Namespaces eingeschlossen, um die Verwendung in Projekten zu vereinfachen (wenn dies jemandem überflüssig erscheint, können Sie sie jederzeit entfernen).
  • Der Noexcept-Bezeichner wurde an einigen Stellen hinzugefügt .

Allen, die hier zumindest diagonal gelesen haben, einen niedrigen Bogen. Ich lege den gesamten Code bei; es kann auch hier genommen werden (mit den neuesten Verbesserungen).

Ganzer Code
./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. Hier und früher im Artikel wird unter VC ++ 14 geschriebener Code angegeben . Aus Gründen der Kompatibilität mit anderen Compilern ist es besser, den Code vom Link zu übernehmen. Besonderer Dank geht an Cheater für die Kompatibilität mit dem GCC .
UPD2. Vielen lexxmark , das sah ein Loch in das Gewinde in Bezug auf mehrere gleichzeitige Ereignisse Anrufe.
Kleinere Verbesserungen
 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. Dank isnullxbh wurde ein weiterer Fehler gefunden. Dies ist mit einer unsachgemäßen Speicherung und dem anschließenden Zugriff auf Objekte verbunden, die von rvalue übergeben werden (hauptsächlich Lambda-Ausdrücke).
Korrektur
, 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 ); } 

Fertig. FunctorHolder . . - .

PS Vergleich mit dem Qt-Signal / Slot-Mechanismus


Ich denke, ich werde mich nicht irren, wenn ich sage, dass Qt ein sehr verbreitetes Framework für die Entwicklung in C ++ ist . Unter anderem verfügt es über einen eigenen Ereignisverarbeitungsmechanismus , bei dem Signale als Analoga von Ereignissen und Slots als Analoga von Handlern vorhanden sind. Es wird mit dem Meta-Object Compiler implementiert , der Teil des globaleren Meta-Object-Systems ist , das wiederum mit dem in Qt über C ++ verwendeten Add-On implementiert wird .

Merkmale beider Implementierungen:


  • die Fähigkeit, Signale (Ereignisse) mit Methoden (Mitgliedsfunktionen), Funktoren und Funktionen zu verbinden;
  • () (), ( 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 ;
  • Verwendung von zusätzlich generiertem Code durch moc ;
    Dies ist bereits völlig subjektiv, aber die Entscheidung ist, wo für jede Klasse, die Signale und Slots verwendet (Slots sind nicht immer), mehrere (pro Datei für jede Konfiguration) generierte Dateien vorhanden sind, die einige Unannehmlichkeiten verursachen. aber um ehrlich zu sein, ist dies der kleinste Fehler.

Es ist wichtig anzumerken, dass dieser Vergleich mit Qt dennoch sehr subjektiv ist und nicht darauf abzielt, diesen Rahmen zu erhöhen oder zu verurteilen. Es muss beachtet werden, dass Qt zusätzlich zum Signal- / Slot-Mechanismus eine hervorragende Funktionalität bietet, sowohl unter Verwendung dieses Mechanismus als auch unabhängig davon. In jedem Fall liegt es immer an Ihnen, zu entscheiden, was Sie verwenden und was nicht.

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


All Articles