C ++ 11 und Ereignisbehandlung

    Ich denke, dass die Ereignisbehandlung als eine Art der Interaktion mit Objekten in OOP fast jedem bekannt ist, der OOP jemals berührt hat. Zumindest ist diese Herangehensweise für ein sehr breites, meiner Meinung nach sehr breites Aufgabenspektrum sehr praktisch. In vielen Programmiersprachen ist ein Mechanismus zur Ereignisbehandlung integriert. In C ++ gibt es jedoch keinen solchen Mechanismus. Mal sehen, was man dagegen tun kann.

    Kurze Einführung


    Ein Ereignis kann unter bestimmten Bedingungen einem bestimmten Objekt passieren (z. B. einer Schaltfläche, wenn Sie mit der Maus darauf klicken). Andere Objekte müssen dies möglicherweise beachten. dann abonnieren sie die Veranstaltung . In diesem Fall wird beim Auftreten eines Ereignisses der Handler eines Objektes eines Drittanbieters aufgerufen , das das Ereignis abonniert hat. somit ist es ihm möglich, Code auszuführen, d.h. auf das Ereignis reagieren Ebenso kann ein Objekt ein Ereignis abbestellen, wenn es nicht mehr darauf reagieren möchte. Infolgedessen haben wir viele Objekte, die durch die Ereignisse eines dieser Objekte und die Reaktion auf diese Ereignisse anderer miteinander verbunden werden können.

    Irgendwie weiß das jeder.

    Die einfachste Implementierung


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

    template<class ...TParams>
    classAbstractEventHandler
    {public:
            virtualvoidcall( TParams... params )= 0;
        protected:    
            AbstractEventHandler() {}
    };
    

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

    template<classTObject, class ...TParams>
    classMethodEventHandler :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 );
            }
            virtualvoidcall( TParams... params ) override final
            {
                ( m_object.*m_method )( params... );
            }
        private:
            TObject& m_object;
            TMethod m_method;
    };
    template<classTObject, 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 Falls sollte sein:

    classTestWindow
    {
        . . .
        public:
            TEvent<conststd::string&, unsignedint> onButtonClick;
        . . .
    };
    classClickEventHandler
    {
        . . .
        public:
            voidtestWindowButtonClick( conststd::string&, unsignedint ){ ... }
        . . .
    };
    intmain( int argc, char *argv[] ){
        . . .
        TestWindow testWindow;
        ClickEventHandler clickEventHandler;
        testWindow.onButtonClick += METHOD_HANDLER( clickEventHandler, ClickEventHandler::testWindowButtonClick );
        . . .
    }
    

    Natürlich ist eine Handler-Methode (eine Member-Funktion einer Klasse) nicht der einzige Typ von Handler, aber dazu später mehr.

    Es scheint alles bequem, kompakt und cool zu sein. Es gibt aber eine Reihe von Mängeln.

    Handler-Vergleich


    Um die Abmeldung eines Ereignisses zu implementieren, müssen Sie dem Handler eine Vergleichsfunktion hinzufügen (at == und ! == ). Solche Handler werden als gleich betrachtet, wenn sie dieselbe Methode (-funktionsmitglied einer Klasse) desselben Objekts (d. H. Dieselbe Instanz derselben Klasse) aufrufen.

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

    template<classTMethodHolder, class ...TParams>
    classMethodEventHandler :public AbstractEventHandler<TParams...>
    {
        . . .
        using TMethod = void( TObject::* )( TParams... );
        protected:
            virtualboolisEquals( 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 der gleichen (gleichen) Handler untersagt werden.

    template<class ...TParams>
    classTEvent
    {
        . . .
        using TEventHandler = AbstractEventHandler<TParams...>;
        using TEventHandlerIt = typenamestd::list<TEventHandler*>::const_iterator;
        public:
            booloperator+=( TEventHandler& eventHandler )
            {
                if( findEventHandler( eventHandler ) == m_handlers.end() )
                {
                    m_handlers.push_back( &eventHandler );
                    returntrue;
                }
                returnfalse;
            }
            booloperator-=( TEventHandler& eventHandler )
            {
                auto it = findEventHandler( eventHandler );
                if( it != m_handlers.end() )
                {
                    TEventHandler* removedEventHandler = *it;
                    m_handlers.erase( it );
                    delete removedEventHandler;
                    returntrue;
                }
                returnfalse;
            }
        private:
            inline TEventHandlerIt findEventHandler( TEventHandler& eventHandler )const{
                returnstd::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 true zurück, wenn er erfolgreich war, und false, wenn die entsprechende Aktion (Hinzufügen oder Entfernen) nicht ausgeführt wurde.

    Ja, der Anwendungsfall mit Vergleich impliziert die Erstellung von temporären, nicht hinzugefügten Handlern, die nirgendwo gelöscht werden. Aber dazu später mehr.

    Kann ich es benutzen? Noch nicht vollständig.

    Löschen Sie den Handler im Handler


    Wir stoßen also bei der Ausführung des Codes sofort auf einen Absturz, bei dem sich der Handler selbst vom Ereignis abschreibt (ich denke, es ist nicht der seltenste Anwendungsfall, wenn der Handler unter allen Umständen selbst spuckt):

    classTestWindow
    {
        . . .
        public:
            TEvent<conststd::string&, unsignedint> onButtonClick;
            static TestWindow& instance();
        . . .
    };
    classClickEventHandler
    {
        . . .
        public:
            voidtestWindowButtonClick( conststd::string&, unsignedint ){
                TestWindow::instance().onButtonClick -= MY_METHOD_HANDLER( ClickEventHandler::testWindowButtonClick );
            }
        . . .
    };
    intmain( 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 durchläuft (mit Hilfe von Iteratoren) die Handler und ruft sie auf.
    • Ein anderer Handler im Inneren bewirkt, dass er selbst gelöscht wird.
    • event entfernt den angegebenen Handler und macht den entsprechenden Iterator ungültig;
    • Nachdem dieser Handler abgeschlossen ist, kehrt das Ereignis zum Rest zurück, der aktuelle Iterator (der dem Fernhandler entspricht) ist jedoch nicht mehr gültig.
    • Ein Ereignis versucht, auf einen ungültigen Iterator zuzugreifen, wodurch ein Absturz verursacht wird.

    Daher ist es notwendig, die Fälle zu überprüfen, in denen die Liste der Handler geändert werden kann, was zur Ungültigkeit der Iteratoren führen würde. und implementieren Sie dann einen Leseschutz für solche Iteratoren.

    Der Vorteil von std :: list 'und in dieser Anwendung ist die Tatsache, dass beim Löschen nur ein Iterator ungültig wird - pro Remote-Element (z. B. die nachfolgenden Elemente). und das Hinzufügen eines Elements führt nicht zu ungültigen Iteratoren. Daher müssen wir einen einzelnen Fall prüfen: das Entfernen eines Elements, dessen Iterator aktuell ist, in der aktuellen Iteration von Elementen. In diesem Fall können Sie beispielsweise das Element nicht löschen, sondern einfach markieren, dass das aktuelle Element gelöscht werden soll, und die Suche innerhalb der Elemente durchführen lassen.

    Es wäre möglich, die Implementierung sofort umzusetzen, aber ich schlage vor, dieses Problem zusammen mit dem Folgenden zu lösen.

    Fadensicherheit


    Aufrufe von drei möglichen Funktionen - Hinzufügen, Löschen und Wiederholen (wenn ein Ereignis ausgelöst wird) - Handler sind potenziell von verschiedenen Threads zu beliebigen Zeitpunkten möglich. Dies schafft ein ganzes Feld von Möglichkeiten für ihre zeitliche "Überschneidung", die ihre Leistung "überlagern" und am Ende den Sturz des Programms "überlagern". Versuchen Sie dies zu vermeiden. Mutexe sind unser Alles .

    template<class ...TParams>
    classTEvent
    {using TEventHandler = AbstractEventHandler<TParams...>;
        using TEventHandlerIt = typenamestd::list<TEventHandler*>::const_iterator;
        public:
            TEvent() :
                m_handlers(),
                m_currentIt(),
                m_isCurrentItRemoved( false ),
                m_handlerListMutex()
            {
            }
            voidoperator()( 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();
            }
            booloperator+=( TEventHandler& eventHandler )
            {
                std::unique_lock<std::shared_mutex> _handlerListMutexLock( m_handlerListMutex );
                if( findEventHandler( eventHandler ) == m_handlers.end() )
                {
                    m_handlers.push_back( std::move( eventHandler ) );
                    returntrue;
                }
                returnfalse;
            }
            booloperator-=( 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 );
                    returntrue;
                }
                returnfalse;
            }
        private:      
            // использовать под залоченным для чтения 'm_handlerListMutex'inline TEventHandlerIt findEventHandler( TEventHandler& eventHandler )const{
                returnstd::find_if( m_handlers.cbegin(), m_handlers.cend(), [ &eventHandler ]( const TEventHandler* oneHandler )
                {
                    return ( *oneHandler == eventHandler );
                } );
            }
            // использовать под залоченным для записи 'm_handlerListMutex'inlinevoiddeleteHandler( TEventHandlerIt it ){
                TEventHandler* removedEventHandler = *it;
                m_handlers.erase( it );
                delete removedEventHandler;
            }
            std::list<TEventHandler*> m_handlers;
            // использовать под залоченным 'm_handlerListMutex'mutable TEventHandlerIt m_currentIt;
            mutablebool m_isCurrentItRemoved;
            mutablestd::shared_mutex m_handlerListMutex;
    };
    

    Vergessen Sie nicht, das "Fenster" nezalochennoti zu verlassen, wenn Sie jeden Handler aufrufen. Dies ist notwendig, damit Sie innerhalb des Handlers auf das Ereignis zugreifen und es modifizieren können (z. B. Handler hinzufügen / entfernen), ohne einen Deadlock zu verursachen . Sie sollten keine Angst vor der Gültigkeit der Daten haben, denn wie wir herausgefunden haben, ist das einzige, was dazu führt, das Löschen des aktuellen Elements, und diese Situation wird behandelt.
    UPD1. Danke, Cheater , vorgeschlagen, dass std :: shared_mutex nur in C ++ 17 (und std :: shared_lock nur in C ++ 14 ) angezeigt wird . Diejenigen, für die es kritisch ist, werden anscheinend mit std :: mutex zu tun haben .
    UPD2.Weiter zur Gewindesicherheit (ohne Beibehaltung der Erzählsequenz).

    Sichtbarkeitsproblem bei Ereignissen


    Wenn Sie ein Ereignis als Member einer Klasse verwenden, erscheint es logisch, es als öffentlich zu machen, damit Objekte von Drittanbietern ihre Handler hinzufügen oder entfernen können. Dies führt jedoch zu operator () , d. H. Das Ereignis ist auch von außen zugänglich, was in einigen Fällen nicht akzeptabel ist. Lösen wir dieses Problem, indem Sie aus der Ereignisklasse ( TEvent <...> ) eine abstrakte Schnittstelle extrahieren , die nur für den Betrieb von Handlern gedacht ist.

    template<class ...TParams>
    classIEvent
    {protected:
            using TEventHandler = AbstractEventHandler<TParams...>;
        public:
            booloperator+=( TEventHandler& eventHandler )
            {
               return addHandler( eventHandler );
            }
            booloperator-=( TEventHandler& eventHandler )
            {
                return removeHandler( eventHandler );
            }
        protected:
            IEvent() {}
            virtualbooladdHandler( TEventHandler& eventHandler )= 0;
            virtualboolremoveHandler( TEventHandler& eventHandler )= 0;
    };
    

    template<class ...TParams>
    classTEvent :public IEvent<TParams...>
    {
        . . .
        public:
            TEvent() :
                IEvent<TParams...>()
                . . .
            {
            }
        protected:
            virtualbooladdHandler( TEventHandler& eventHandler ) override
            {
                // код, который был ранее в 'TEvent::operator+='
            }
            virtualboolremoveHandler( TEventHandler& eventHandler ) override
            {
                // код, который был ранее в 'TEvent::operator-='
            }
        . . .
    };
    

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

    classTestWindow
    {
        . . .
        public:
            TestWindow() :
                onButtonClick( m_onButtonClick ),
                m_onButtonClick()
            {
            }
            IEvent<conststd::string&, unsignedint>& onButtonClick;
        protected:
            TEvent<conststd::string&, unsignedint> m_onButtonClick;
        . . .
    };
    

    So können Objekte von Drittanbietern nun ihre Handler über TestWindow :: onButtonClick hinzufügen / entfernen , jedoch können sie dieses Ereignis nicht selbst auslösen. Der Aufruf kann jetzt nur innerhalb der Klasse TestWindow (und ihrer Nachkommen, wenn der Gültigkeitsbereich des Ereignisses als Beispiel geschützt ist ) erfolgen.

    Der triviale Code wird allmählich zu etwas Ungeheuerlichem, aber das ist nicht das Ende.

    Anpassen der Parameter des Ereignisses und seiner Handler


    In der aktuellen Implementierung müssen der Event- und Event-Handler über eine streng entsprechende Liste von Parametern verfügen. Dies führt zu mehreren Nachteilen.

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

    template<classTSource>
    classMyClass
    {
        . . .
        public:
            TEvent<const TSource&> onValueChanged;
        . . .
    };
    

    Da der hier verwendete Typ im Voraus nicht bekannt ist, ist es sinnvoll, ihn durch eine konstante Referenz und nicht durch einen Wert zu übergeben. Für jede Implementierung müssen nun jedoch auch bei grundlegenden Typen entsprechende Handler vorhanden sein.

    MyClass<bool> myBoolClass;
    . . .
    template<classTSource>
    classMyHandlerClass
    {
        . . .
        private:
            voidhandleValueChanged1( constbool& newValue );
            voidhandleValueChanged2( bool newValue );
        . . .
    };
    . . .
    MyHandlerClass myHandlerClass;
    myBoolClass.onValueChanged += METHOD_HANDLER( myHandlerClass, MyHandlerClass::handleValueChanged1 );  // OK
    myBoolClass.onValueChanged += METHOD_HANDLER( myHandlerClass, MyHandlerClass::handleValueChanged2 );  // compile error

    Ich möchte in der Lage sein, sich mit einem ähnlichen Ereignis und Handlern der Form MyHandlerClass :: handleValueChanged2 zu verbinden , aber bisher besteht keine solche Möglichkeit.

    Die zweite Versuchen wir, einen Handler-Funktionscode auf dieselbe Weise wie eine vorhandene Handler-Methode (-Funktionsmitglied einer Klasse) zu implementieren.

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

    Jetzt werden wir versuchen, es mit einem Ereignis zu vermasseln.

    classTestWindow
    {
        . . .
        public:
            TEvent<conststd::string&, unsignedint> onButtonClick;
        . . .
    };
    structClickEventHandler
    {voidoperator()( conststd::string&, unsignedint ){ . . . }
    };
    intmain( int argc, char *argv[] ){
        . . .
        TestWindow testWindow;
        ClickEventHandler clickEventHandler;
        testWindow.onButtonClick += FUNCTOR_HANDLER( clickEventHandler );
        . . .
    }
    

    Das Ergebnis wird ein Kompilierungsfehler sein. Für die createFunctorEventHandler- Funktion kann der Compiler die TParam- Typen nicht aus dem einzigen Argument dieser Funktion ableiten - dem Funktor selbst. Der functor enthält wirklich keine Informationen darüber, welchen Prozessortyp Sie erstellen müssen. Das einzige, was in dieser Situation getan werden kann, ist etwas zu schreiben:

    testWindow.onButtonClick += createFunctorEventHandler<ClickEventHandler, conststd::string&, unsignedint>( clickEventHandler );
    

    Aber das wollen Sie überhaupt nicht.

    Verbindung von Ereignissen mit Handlern verschiedener Typen


    Es gibt also eine Wunschliste, es liegt an der Implementierung. Wir werden die Situation am Beispiel eines Handler-Funktors betrachten, eine Handler-Methode (-Funktionsmitglied einer Klasse) wird sich auf dieselbe Weise herausstellen.

    Da es auf der Basis des Funktors allein nicht möglich ist, die Liste der Parameter des entsprechenden Handlers anzugeben, werden wir dies nicht tun. Dieses Problem wird nicht zum Zeitpunkt der Erstellung des Handlers relevant, sondern zum Zeitpunkt des Versuchs, es mit einem bestimmten Ereignis zu verknüpfen. Und ja, das sind zwei verschiedene Punkte. Sie können diese Idee folgendermaßen implementieren:

    template<classTFunctor> classFunctorHolder;template<classTFunctor, class ...TParams>
    classFunctorEventHandler :public AbstractEventHandler<TParams...>
    {
        public:
            FunctorEventHandler( FunctorHolder<TFunctor>& functorHolder ) :
                AbstractEventHandler<TParams...>(),
                m_functorHolder( functorHolder )
            {
            }
            virtualvoidcall( TParams... params ) override
            {
                m_functorHolder.m_functor( params... );
            }    
        private:
            FunctorHolder<TFunctor>& m_functorHolder;
        . . .
    };
    

    template<classTFunctor>
    classFunctorHolder
    {public:
            FunctorHolder( TFunctor& functor ) :
                m_functor( functor )
            {
            }
            template<class ...TCallParams>
            operatorAbstractEventHandler<TCallParams...>&()
            {return *new FunctorEventHandler<TFunctor, TCallParams...>( *this );
            }
        private:
            TFunctor& m_functor;
        . . .
        template<classTFunctor, class ...TParams> friendclassFunctorEventHandler;
    };
    

    template<classTFunctor>
    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>
    classIEvent
    {protected:
            using TEventHandler = AbstractEventHandler<TParams...>;
        public:
            template<classTSome>
            booloperator+=( TSome&& some )
            {return addHandler( static_cast<TEventHandler&>( some ) );
            }
            template<classTSome>
            booloperator-=( TSome&& some )
            {return removeHandler( static_cast<TEventHandler&>( some ) );
            }
        protected:
            IEvent() {}
            virtualbooladdHandler( TEventHandler& eventHandler )= 0;
            virtualboolremoveHandler( TEventHandler& eventHandler )= 0;
    };
    

    Kurz gesagt, die Trennung der Momente, in denen ein Handler erstellt und an ein Ereignis angehängt wird, ist deutlicher als zuvor. Dadurch können Sie die im vorherigen Abschnitt beschriebenen Probleme umgehen. Eine Typkompatibilitätsprüfung findet statt, wenn versucht wird, einen bestimmten FunctorHolder einem bestimmten FunctorEventHandler zuzuordnen oder vielmehr eine Instanz der Klasse FunctorEventHandler <....> mit einem ganz bestimmten Typ von Funktortyp zu erstellen . In dieser Klasse gibt es eine Zeile mit dem Code m_functorHolder.m_functor (params ...); , die einfach nicht für eine Gruppe von Typen kompiliert wird, die nicht mit einem Funktor kompatibel sind (oder, wenn es überhaupt kein Funktor ist, d. h. ein Objekt ohne Operator () ).

    Ich wiederhole noch einmal, dass das Problem des Löschens temporärer Objekte weiter unten erläutert wird. Darüber hinaus ist es erwähnenswert, dass für jeden Fall eine Reihe von Makros erstellt wird, um erstens die Fähigkeiten dieses Handlertyps zu demonstrieren, und zweitens im Falle einer möglichen Änderung einer dieser Dateien durch eine Datei.

    Überprüfen Sie das Ergebnis.

    classTestWindow
    {
        . . .
        public:
            TEvent<conststd::string&, unsignedint> onButtonClick;
        . . .
    };
    structFunctor
    {voidoperator()( conststd::string&, unsignedint ){}
    };
    structFunctor2
    {voidoperator()( std::string, unsignedint ){}
    };
    structFunctor3
    {voidoperator()( conststd::string&, constunsignedint& ){}
    };
    structFunctor4
    {voidoperator()( std::string, constunsignedint& ){}
    };
    structFunctor5
    {voidoperator()( std::string&, unsignedint& ){}
    };
    structFunctor6
    {voidoperator()( conststd::string&, unsignedint& ){}
    };
    structFunctor7
    {voidoperator()( std::string&, constunsignedint& ){}
    };
    intmain( 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
        . . .
    }
    

    Ein Kompilierungsfehler tritt auf, wenn versucht wird, einen der Parameter von const lvalue nach lvalue zu konvertieren . Das Konvertieren von rvalue in unconst lvalue verursacht keinen Fehler, obwohl es erwähnenswert ist, dass eine potenzielle Bedrohung für einen Selbstschuss im Bein entsteht: Der Handler kann die auf den Stapel kopierte Variable ändern, die beim Verlassen des Handlers glücklich entfernt 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 &'
    

    Wenn Sie Ereignisse und Handler in Code von Drittanbietern verwenden, können Sie zur besseren Übersichtlichkeit Ihre eigene Fehlermeldung hinzufügen. Dies erfordert das Schreiben einer kleinen Hilfsstruktur (ich gebe zu, ich habe irgendwo einen ähnlichen Ansatz entdeckt):

    namespace
    {
        template<classTFunctor, class ...TParams>
        structIsFunctorParamsCompatible
        {private:
                template<classTCheckedFunctor, class ...TCheckedParams>
                staticconstexprstd::true_type exists( decltype( std::declval<TCheckedFunctor>()( std::declval<TCheckedParams>()... ) )* = nullptr );
                template<classTCheckedFunctor, class ...TCheckedParams>
                staticconstexprstd::false_type exists( ... );
            public:
                staticconstexprbool value = decltype( exists<TFunctor, TParams...>( nullptr ) )::value;
        };
    } //

    template<classTFunctor, class ...TParams>
    classFunctorEventHandler :public AbstractEventHandler<TParams...>
    {
        . . .
        public:
            virtualvoidcall( 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 existierende Funktion zu kompilieren. Wenn dies jedoch aufgrund der Inkompatibilität der Argumente (oder des Fehlens von operator () dessen, was als Funktionsweise übergeben wird) fehlschlägt , gibt der Compiler keinen Fehler aus, sondern versucht lediglich, die zweite Funktion zu kompilieren. Wir tun alles, damit die Kompilierung immer erfolgreich ist. In der Tat, welche der Funktionen kompiliert wurde, schließen wir (Schreiben des Ergebnisses in den Wert ) über die Kompatibilität der Argumente für die angegebenen Typen.

    Jetzt sieht die Fehlermeldung 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 der Argumente von rvalue in unconst lvalue : Jetzt wird ein Fehler inkompatibler Argumente verursacht, d. H. Ein Versuch, den functor6- Handler aus dem obigen Beispiel hinzuzufügen, führt zu einem Fehler bei der Kompilierung.
    UPD. Revision (ohne die Reihenfolge der Geschichte zu erhalten).

    Vergleich von Funktoren


    Aufgrund von Änderungen im Klassenhandler ändert sich die Implementierung des Vergleichs von Instanzen dieser Klasse etwas. Auch hier werde ich nur die Implementierung eines Handler-Funktors geben, da eine Handler-Methode (-Funktion-Member einer Klasse) ähnlich aussehen wird.

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

    template<classTFunctor, class ...TParams>
    classFunctorEventHandler :public AbstractEventHandler<TParams...>
    {
        . . .
        using MyType = FunctorEventHandler<TFunctor, TParams...>;
        protected:
            virtualboolisEquals( 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<classTFunctor>
    classFunctorHolder
    {
        . . .
        using MyType = FunctorHolder<TFunctor>;
        public:
            booloperator==( const MyType& other ) const
            {
                return ( m_functor == other.m_functor );
            }
            booloperator!=( const MyType& other ) const
            {
                return !( *this == other );
            }
        private:
            TFunctor& m_functor;
        . . .
    };
    

    Bei dieser Ähnlichkeit in der Umsetzung endet der Vergleich und beginnt zum Teil nur für Handler-Funktoren.

    Wie oben erwähnt, haben wir verschiedene Arten von Handlers-Funktionen erhalten: direkt Objekte-Funktionen, Lambda-Ausdrücke, Instanzen der Klasse std :: function und separate Funktionen. Von diesen können Functor-Objekte, Lambda-Ausdrücke und Instanzen der std :: -Funktionsklasse nicht mit dem Operator == verglichen werden (sie müssen anhand der Adresse verglichen werden), einzelne Funktionen jedoch, weil bereits gespeichert bei. Um die Vergleichsfunktion nicht für jeden Fall separat zu schreiben, schreiben wir sie in allgemeiner Form:

    namespace
    {
        template<classTEqu, classTEnabled = void>
        structEqualityChecker;template<classTEquatable>
        structEqualityChecker<TEquatable, typename std::enable_if<is_equatable<TEquatable>::value>::type>
        {staticconstexprboolisEquals( const TEquatable& operand1, const TEquatable& operand2 ){
                return ( operand1 == operand2 );
            }
        };
        template<classTNonEquatable>
        structEqualityChecker<TNonEquatable, typename std::enable_if<!is_equatable<TNonEquatable>::value>::type>
        {staticconstexprboolisEquals( const TNonEquatable& operand1, const TNonEquatable& operand2 ){
                return ( &operand1 == &operand2 );
            }
        };
    } //template<classTFunctor>
    classFunctorHolder
    {
        . . .
        using MyType = FunctorHolder<TFunctor>;
        public:
            booloperator==( const MyType& other ) const
            {
                return EqualityChecker<TFunctor>::isEquals( m_functor, other.m_functor );
            }
        private:
            TFunctor& m_functor;
        . . .
    };
    

    Es wird impliziert, dass is_equatable eine Hilfsvorlage ist, die bestimmt, ob zwei Instanzen eines bestimmten Typs auf Gleichheit geprüft werden können. Dabei wählen wir mit std :: enable_if eine von zwei teilweise spezialisierten Strukturen aus, EqualityChecker , die den Vergleich durchführen: nach Wert oder nach Adresse. Implementiert is_equatable kann es wie folgt sein:

    template<classT>
    classis_equatable
    {private:
            template<classU>
            staticconstexprstd::true_type exists( decltype( std::declval<U>() == std::declval<U>() )* = nullptr );
            template<classU>
            staticconstexprstd::false_type exists( ... );
        public:
            staticconstexprbool value = decltype( exists<T>( nullptr ) )::value;
    };
    

    Diese Implementierung basiert auf dem SFINAE- Mechanismus , der zuvor verwendet wurde . Nur hier überprüfen wir die Verfügbarkeit des Operators == für Instanzen einer bestimmten Klasse.

    Diese einfache Möglichkeit, den Vergleich von Handlers-Funktoren zu implementieren, ist fertig.

    Müllsammlung


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

    Wir nähern uns dem Finale, und es ist an der Zeit, die große Anzahl von Objekten zu beseitigen, die von niemandem erstellt werden.

    Bei jedem Ereignis des Ereignisses mit dem Handler werden zwei Objekte erstellt: Holder , der den ausführbaren Teil des Handlers speichert, und EventHandleres einem Ereignis zuordnen. Vergessen Sie nicht, dass beim Versuch, den Handler erneut hinzuzufügen, keine Hinzufügung erfolgt - zwei Objekte "hängen in der Luft" (es sei denn, dies wird natürlich jedes Mal separat geprüft). Eine andere Situation: Handler löschen; Es werden auch zwei neue Objekte erstellt, um in der Liste der Event-Handler nach demselben (gleichwertigen) zu suchen. Der gefundene Handler aus der Liste wird natürlich gelöscht (falls vorhanden), und dieser temporäre, für die Suche erstellte und aus zwei Objekten bestehende Vorgang befindet sich wieder in der Luft. Im Allgemeinen nicht cool.

    Wenden Sie sich an intelligente Zeiger . Es muss festgestellt werden, welche Semantik des Eigentums für jedes der beiden Handler-Objekte gilt: Alleineigentum ( std :: unique_ptr ) oder shared ( std :: shared_ptr ).

    HalterNeben der Verwendung des Ereignisses selbst beim Hinzufügen / Löschen sollte es im EventHandler gespeichert werden , daher verwenden wir für den geteilten Besitz und für den EventHandler den Alleineigentum, da Nach der Erstellung wird es nur in der Liste der Event-Handler gespeichert.

    Implementieren Sie diese Idee:

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

    namespace
    {
        template<classTSome>
        structHandlerCast
        {template<class ...Types>
            staticconstexprTHandlerPtr<Types...> cast( TSome& some )
            {returnstatic_cast<THandlerPtr<Types...>>( some );
            }
        };
        template<classTPtr>
        structHandlerCast<std::shared_ptr<TPtr>>
        {template<class ...Types>
            staticconstexprTHandlerPtr<Types...> cast( std::shared_ptr<TPtr> some )
            {
                return HandlerCast<TPtr>::cast<Types...>( *some );
            }
        };
    } //template<class ...TParams>
    classIEvent
    {public:
            template<classTSome>
            booloperator+=( TSome&& some )
            {return addHandler( HandlerCast<TSome>::cast<TParams...>( some ) );
            }
            template<classTSome>
            booloperator-=( TSome&& some )
            {return removeHandler( HandlerCast<TSome>::cast<TParams...>( some ) );
            }
        protected:
            using TEventHandlerPtr = THandlerPtr<TParams...>;
            IEvent() {}
            virtualbooladdHandler( TEventHandlerPtr eventHandler )= 0;
            virtualboolremoveHandler( TEventHandlerPtr eventHandler )= 0;
    };
    template<class ...TParams>
    classTEvent :public IEvent<TParams...>
    {
        using TEventHandlerIt = typenamestd::list<TEventHandlerPtr>::const_iterator;
        public:
            TEvent()
            {
                . . .
            }
            ~TEvent()
            {
                // empty
            }
        protected:
            virtualbooladdHandler( 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 ) );
                    returntrue;
                }
                returnfalse;
            }
            virtualboolremoveHandler( TEventHandlerPtr eventHandler ) override
            {
                . . .
            }
        private:
            // использовать под залоченным для чтения 'm_handlerListMutex'inline TEventHandlerIt findEventHandler( const TEventHandlerPtr& eventHandler )const{
                returnstd::find_if( m_handlers.cbegin(), m_handlers.cend(), [ &eventHandler ]( const TEventHandlerPtr& oneHandler )
                {
                    return ( *oneHandler == *eventHandler );
                } );
            }
            // использовать под залоченным для записи 'm_handlerListMutex'inlinevoiddeleteHandler( TEventHandlerIt it ){
                m_handlers.erase( it );
            }
            std::list<TEventHandlerPtr> m_handlers;
        . . .
    };
    

    template<classTMethodHolder, class ...TParams>
    classMethodEventHandler :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<classTObject, class ...TParams>
    classMethodHolder
    {using MyType = MethodHolder<TObject, TParams...>;
        using TMethod = void( TObject::* )( TParams... );
        public:
            MethodHolder( TObject& object, TMethod method )
            {
                . . .
            }
            template<class ...TCallParams>
            operatorTHandlerPtr<TCallParams...>()
            {return THandlerPtr<TCallParams...>( new MethodEventHandler<MyType, TCallParams...>( /* ЧТО СЮДА ПЕРЕДАТЬ? */ ) );
            }
        . . .
    };
    template<classTObject, class ...TParams>
    std::shared_ptr<MethodHolder<TObject, TParams...>> createMethodEventHandler( TObject& object, void( TObject::*method )( TParams... ) )
    {
        returnstd::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 )

    Alles in Ordnung.

    Start des Ereignisses und seiner Schnittstelle zum Arbeiten mit Handlern. In letzterem Fall wird das Konvertieren von Typen durch direktes Verwenden von static_cast nicht mehr funktionieren, da der zu konvertierende Typ "in" std :: shared_ptr liegt . Für eine solche Umwandlung werden wir nun die Hilfsstruktur von HandlerCast verwenden , die durch ihre private Spezialisierung Zugriff auf das Objekt in std :: shared_ptr gewährt und bereits mit ihr (in ihrer nicht spezialisierten Implementierung) den guten alten static_cast anwendet .

    Die Veranstaltung selbst; Auch hier gibt es einige wichtige Änderungen. Stoppen wir zunächst das manuelle Löschen von Instanzen von Handlern im Destruktor und während des Löschvorgangs. Jetzt reicht es aus, einen intelligenten Zeiger 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 nicht das Kopieren (was für eine solche Semantik recht logisch ist).

    Wir wenden uns an die Handler. Nach alter Tradition wird nur eine gegeben, die zweite ist ähnlich. Auf den ersten Blick müssen Sie die Art der gespeicherten / erstellten Objekte mit Verknüpfungen / Zeigern auf intelligente Zeiger ändern.

    Aber es gibt einen subtilen Punkt. Die Funktion createMethodEventHandler gibt der Instanz std :: shared_ptr zurück .MethodHolder . Wenig später wird versucht, es in einen Handler-Typ ( MethodEventHandler ) zu konvertieren , wo eine neue MethodEventHandler- Instanz erstellt und an den Konstruktor std :: shared_ptr an sich selbst übergeben werden muss. Auf diese Weise sollte die MethodHolder- Instanz später zurückgezogen werden, 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 gespeichert ist .

    Um das Problem zu lösen, müssen Sie in MethodHolder einen intelligenten Zeiger speichern . Damit er seine Entfernung jedoch nicht beeinträchtigt, verwenden wir siestd :: weak_ptr :

    template<classTObject, class ...TParams>
    classMethodHolder
    {using MyType = MethodHolder<TObject, TParams...>;
        using TMethod = void( TObject::* )( TParams... );
        public:
            template<class ...TCallParams>
            operatorTHandlerPtr<TCallParams...>()
            {return THandlerPtr<TCallParams...>( new MethodEventHandler<MyType, TCallParams...>( m_me.lock() ) );
            }
            template<classTObject, class ...TParams>
            staticstd::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<classTObject, class ...TParams>
    std::shared_ptr<MethodHolder<TObject, TParams...>> createMethodEventHandler( TObject& object, void( TObject::*method )( TParams... ) )
    {
        return MethodHolder<TObject, TParams...>::create( object, method );
    }
    

    Der besseren Übersicht halber gebe ich eine ungefähre Reihenfolge der Ereignisse an, wenn ich einen Handler von einem Ereignis entferne (entschuldige mich für das zufällige Wortspiel):

    • Ein Ereignis löscht ein Element aus der Liste ( m_handlers.erase (it); ), wodurch der Destruktor aufgerufen wird.
    • destructor std :: unique_ptr wird aufgerufen , wodurch der Destruktor des verwalteten Objekts aufgerufen wird;
    • Der Destruktor MethodEventHandler wird aufgerufen , der alle Objektfelder entfernt, einschließlich des Felds m_methodHolder , das std :: shared_ptr ist .
    • Destruktor std :: shared_ptr wird aufgerufen ; er sieht, dass der Besitzerzähler den Wert null erreicht hat (weil er zum Zeitpunkt der Entfernung vom Ereignis der einzige Besitzer war) und ruft den verwalteten Objekt-Destruktor ( MethodHolder ) auf; Die Zerstörung der Steuereinheit wird jedoch nicht aufgerufen, da der Referenzzähler std :: weak_ptr noch nicht Null ist;
    • Es wird der Destruktor MethodHolder aufgerufen , der zur Zerstörung aller Felder führt, einschließlich des Felds m_me , das std :: weak_ptr ist .
    • Destruktor std :: weak_ptr wird aufgerufen ; sein verwaltetes Objekt wurde bereits zerstört; weil Der Referenzzähler std :: weak_ptr wurde gleich Null, die Zerstörung der Steuereinheit wird aufgerufen.
    • Gewinn

    Beachten Sie, dass der Destruktor der AbstractEventHandler- Klasse virtuell sein muss. Andernfalls wird nach Punkt 2 in Punkt 3 der Destruktor von AbstractEventHandler aufgerufen, und es werden keine weiteren Aktionen ausgeführt.

    Ereignis- und Handler-Verbindung


    In manchen Fällen, wenn ein einzelner Handler häufig hinzugefügt / aus einem Ereignis entfernt wird (gemäß einer bestimmten Logik), möchte ich mich nicht darum kümmern, da ich jedes Mal eine Instanz des Ereignisses und eine Instanz des Handlers benutze, um erneut ein Abonnement / Abbestellen dieses Ereignisses zu implementieren. Sie möchten sie einmal verbinden, und dann, falls erforderlich, mit dieser Verbindung arbeiten und dabei den vordefinierten Handler aus einem vorbestimmten Ereignis hinzufügen / entfernen. Sie können dies wie folgt implementieren:

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

    template<class ...TParams>
    classIEvent
    {
        . . .
        protected:
            using TEventHandlerPtr = THandlerPtr<TParams...>;
            virtualboolisHandlerAdded( const TEventHandlerPtr& eventHandler )const= 0;
            virtualbooladdHandler( TEventHandlerPtr eventHandler )= 0;
            virtualboolremoveHandler( TEventHandlerPtr eventHandler )= 0;
        friendclassHandlerEventJoin<TParams...>;
        . . .
    };
    template<class ...TParams>
    classTEvent :public IEvent<TParams...>
    {
        . . .
        protected:
            virtualboolisHandlerAdded( const TEventHandlerPtr& eventHandler )const override
            {
                std::shared_lock<std::shared_mutex> _handlerListMutexLock( m_handlerListMutex );
                return ( findEventHandler( eventHandler ) != m_handlers.end() );
            }
            virtualbooladdHandler( TEventHandlerPtr eventHandler ) override { . . . }
            virtualboolremoveHandler( TEventHandlerPtr eventHandler ) override { . . . }
        private:
            // использовать под залоченным для чтения 'm_handlerListMutex'inline TEventHandlerIt findEventHandler( const TEventHandlerPtr& eventHandler )const{ . . . }
            std::list<TEventHandlerPtr> m_handlers;
            mutablestd::shared_mutex m_handlerListMutex;
        . . .
    };
    

    template<class ...TParams>
    classHandlerEventJoin
    {public:
            HandlerEventJoin( IEvent<TParams...>& _event, THandlerPtr<TParams...> handler ) :
                m_event( _event ),
                m_handler( handler )
            {
            }
            inlineboolisJoined()const{
                return m_event.isHandlerAdded( m_handler );
            }
            inlinebooljoin(){
                return m_event.addHandler( m_handler );        
            }
            inlineboolunjoin(){
                return m_event.removeHandler( m_handler );        
            }
        private:
            IEvent<TParams...>& m_event;
            THandlerPtr<TParams...> m_handler;
    };
    

    Wie Sie sehen, haben wir nun einen weiteren möglichen Speicherort für die Handlerinstanz hinzugefügt. Daher verwenden wir std :: shared_ptr anstelle von std :: unique_ptr .

    Diese Klasse ist jedoch für mich etwas unpraktisch. Es wäre wünschenswert, Instanzen von Verbindungen ohne eine Liste von Parametern, die eine Klassenvorlage instanziieren, zu speichern und zu erstellen.

    Wir machen dies mit Hilfe einer abstrakten Vorfahrklasse und eines Wrappers:

    classAbstractEventJoin
    {public:
            virtual ~AbstractEventJoin() {}
            virtualboolisJoined()const= 0;
            virtualbooljoin()= 0;
            virtualboolunjoin()= 0;
        protected:
            AbstractEventJoin() {}
    };
    

    template<class ...TParams>
    classHandlerEventJoin :public AbstractEventJoin
    {
        . . .
        public:
            virtualinlineboolisJoined()const override { . . . }
            virtualinlinebooljoin() override { . . . }
            virtualinlineboolunjoin() override { . . . }
        . . .
    };
    

    classEventJoinWrapper
    {public:
            template<classTSome, class ...TParams>
            inlineEventJoinWrapper( IEvent<TParams...>& _event, TSome&& handler ) :
                m_eventJoin( std::make_shared<HandlerEventJoin<TParams...>>( _event, HandlerCast<TSome>::cast<TParams...>( handler ) ) )
            {
            }
            constexprEventJoinWrapper() :
                m_eventJoin( nullptr ){
            }
            ~EventJoinWrapper()
            {
                if( m_eventJoin != nullptr )
                    delete m_eventJoin;
            }
            operatorbool()const{
                return isJoined();
            }
            boolisAssigned()const{
                return ( m_eventJoin != nullptr );
            }
            boolisJoined()const{
                return ( m_eventJoin != nullptr && m_eventJoin->isJoined() );
            }
            booljoin(){
                return ( m_eventJoin != nullptr ? m_eventJoin->join() : false );
            }
            boolunjoin(){
                return ( m_eventJoin != nullptr ? m_eventJoin->unjoin() : false );
            }
        private:
            AbstractEventJoin* m_eventJoin;
    };
    using EventJoin = EventJoinWrapper;
    

    HandlerCast ist dieselbe Tragstruktur, die hier verwendet wurde . By the way, ist es wichtig , nicht zu vergessen das destructor zu machen AbstractEventJoin virtuellen, also , wenn Sie eine Instanz in dem destructor entfernen EventJoinWrapper destructor freiwillig HandlerEventJoin , sonst im letzteren Bereich wird nicht zerstört THandlerPtr und damit der Handler selbst.

    Diese Implementierung scheint zu funktionieren, aber nur auf den ersten Blick. Beim Kopieren oder Verschieben einer EventJoinWrapper- Instanz wird m_eventJoin in seinem Destruktor erneut gelöscht . Daher verwenden wir std :: shared_ptr , um die Instanz zu speichern.AbstractEventJoin , sowie leicht optimierte Bewegungssemantik (und Kopieren) implementieren, da Dies ist eine möglicherweise häufige Operation.

    classEventJoinWrapper
    {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 nun den Handler mit dem Ereignis verbinden, können Sie sofort eine Instanz der neuen Verbindung zurückgeben:

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

    Nachdem Sie die dreieckigen Abhängigkeiten mit include (IEvent <= EventJointWrapper.hpp; EventJointWrapper <= HandlerEventJoin.hpp; HandlerEventJoin <= IEvent.hpp) festgelegt haben , können Sie sogar mit dem Aufteilen einiger Dateien in .h und .hpp arbeiten .

    Die Erstellung von Verbindungsinstanzen erfolgt nach den gleichen Regeln wie beim Abonnieren eines Ereignisses durch einen Handler:

    structEventHolder
    {
        TEvent<conststd::string&> onEvent;
    };
    structMethodsHolder
    {voidmethod1( conststd::string& ){}
        voidmethod2( std::string ){}
        voidmethod3( std::string&& ){}
        voidmethod4( std::string& ){}
        voidmethod5( constint& ){}
    };
    
        

    Jetzt auch beliebt: