c++ 11 Event implementation



  • Hallo zusammen.

    Steh mal wieder auf dem Schlauch:

    Habe folgende Event Implementation

    class Event
    {
    public:
        using func = std::function<void(T)>;
    
    
        void Raise(T arg)
        {
            for (auto handler : _handlers)
            {
                handler(arg);
            }        
        }
    
        void operator ()(T arg)
        {
            Raise(arg);
        }
    
        Event& operator += (func f)
        {
            _handlers.push_back(f);
            return *this;
        }
    
        Event& operator -= (func f)
        {
            for (auto handler : _handlers)
            {
                
                if (handler.template target<void(T)>() == f.template target<void(T)>())
                {
                    _handlers.erase(handler);
                    break;
                }
            }
    
            return *this;
        }
    
    private:
        std::vector<func> _handlers;
    };
    

    eine weitere Klasse hat nun ein solches Event

    class SomeClass
    {
     public:
           Event<std::string> SomethingChanged;
    }
    

    möchte ich nun ein Event einhängen in einer weiteren Klasse mache ich das wie folgt.

    class Onemoreclass
    {
         SomeClass _someclass{};
    public: 
       Onemoreclass()
      {
            _someClass.SomethingChanged += [](std::string message) {std::cout << message << endl; }
      }
    }
    

    funktioniert.
    doch wie hänge ich das Event wieder aus.

    dachte ich kann aus dem lambda Ausdruck eine Membermethode wie in c#.
    Aber da klappt schon das einhängen nicht.

    class Onemoreclass
    {
         SomeClass _someclass{};
    public: 
       Onemoreclass()
      {
            _someClass.SomethingChanged  += Print;
      }
    
    private:
        void Print(std::string message)   {std::cout << message << endl; }
    }
    

    Fehler:
    Error C2679: binary '+=': no operator found which takes a right-hand operand of type 'overloaded-function' (or there is no acceptable conversion) (158)

    Wie konvertiere ich das? Kann mir jemand helfen?



  • @booster sagte in c++ 11 Event implementation:

    Wie konvertiere ich das?

    Garnicht. Um Onemoreclass::Print() aufrufen zu können brauchst Du ein Onemoreclass-Objekt. Und noch dazu nimmt Event "nur" einen Function Pointer, keien Member Function Pointer.

    C++11 Generic Observer-Pattern



  • Wie definiere ich die Methode "Print" dann so dass ich sie aus dem Event auch wieder aushängen kann. Das ist ja das was ich eigentlich möchte muss ja keine Member Methode sein.


  • Mod

    @booster sagte in c++ 11 Event implementation:

    Das ist ja das was ich eigentlich möchte muss ja keine Member Methode sein.

    Wieso ist sie dann eine?

    So wie die Methode print derzeit definiert ist, kann sie auch eine freie Methode sein. Und alles, was eine freie Methode sein kann, sollte in aller Regel auch eine freie Methode sein. Ich ging davon aus, dass du dein Beispiel bloß zu sehr abgespeckt hattest, als dass der Sinn der Memberschaft noch ersichtlich war. Aber wenn das wirklich so ist, dann mach sie doch frei! Dann passt auch zur Signatur von Event.

    Ansonsten, falls print wirklich eine Membermethode von etwas sein muss (Das kann schließlich sehr gut vorkommen, dass man solch einen Fall hat, selbst wenn es für print nicht gilt), ist die Wunderwaffe std::bind. Dein Eventhandler will irgendetwas, dass er einfach nur aufrufen muss. Wenn nun deine Funktion mehr braucht als nur ihren Namen, um aufgerufen werden zu können (zum Beispiel einen this-Zeiger, wenn sie eine Memberfunktion ist, aber auch im Falle beliebiger anderer Parameter), dann kann man mit std::bind aus dem Funktionszeiger und den Parametern ein neues funktionsartiges Objekt erzeugen, dass beim Aufruf indirekt die Orginalfunktion mit den gespeicherten Parametern aufruft. So kannst du dann Memberfunktionen eines konkreten Objekts an eine Schnittstelle übergeben, die einen allgemeinen Funktionszeiger erwartet.



  • @SeppJ sagte in c++ 11 Event implementation:

    Wieso ist sie dann eine?

    Die Printmthode ist hier ziemlich abgespeckt, ja.

    In meiner richtigen methode greife ich auf member von der Onemoreclass zu.
    Ich meinte es müsste ja keine member sein ich kann ja auch das Objekt auf Onemoreclass mit this mitgeben.
    Ich habe sie einfach zum member gemacht. In erster Linie geht es mir ja darum dass ich die Methode auch wieder aushängen kann.

    std::bind habe ich getestet. Aber hat nicht funktioniert.
    Ich weiß hat nicht funktioniert ist keine Beschreibung 🙂

    ... moment...

    _someClass.SomethingChanged  += std::bind(Print, this);
    

    Fehler:

    Error C2672: 'std::bind': no matching overloaded function found (160)
    Error C3867: 'Onemoreclass::Print': non-standard syntax; use '&' to create a pointer to member (160)

    ->

    _someClass.SomethingChanged  += std::bind(&Print, this);
    

    Fehler:

    Error C2276: '&': illegal operation on bound member function expression (160)



  • _someClass.SomethingChanged  += std::bind(&Onemoreclass::Print, this);
    

    (s.a. Beispiel bzgl. &Foo::print_sum in std::bind)

    PS: Fehlt bei deiner Event-Klasse nicht template <typename T> (oder woher kommt T)?



  • @Th69 sagte in c++ 11 Event implementation:

    (s.a. Beispiel bzgl. &Foo::print_sum in std::bind)

    _someClass.SomethingChanged  += std::bind(&Onemoreclass::Print, this);
    

    ja stimmt. Habe ich aber auch schon getestet:

    Fehler:
    Error C2679: binary '+=': no operator found which takes a right-hand operand of type 'std::_Binder<std::_Unforced,void (__thiscall Onemoreclass::* )(std::string),Onemoreclass *>' (or there is no acceptable conversion) (160)

    @Th69 sagte in c++ 11 Event implementation:

    PS: Fehlt bei deiner Event-Klasse nicht template <typename T> (oder woher kommt T)?

    Ist doch definiert in Someclass

    Event<std::string> SomethingChanged;
    


  • @booster sagte in c++ 11 Event implementation:

    PS: Fehlt bei deiner Event-Klasse nicht template <typename T> (oder woher kommt T)?

    @booster sagte in c++ 11 Event implementation:

    Ist doch definiert in Someclass
    Eventstd::string SomethingChanged;

    Blödsinn. Du meintest was anderes:

    template<typename T>
    class Event
    

    Habe ich vergessen zu kopieren. Sorry.
    Aber das ist nicht das Problem



  • @booster sagte in c++ 11 Event implementation:

    _someClass.SomethingChanged += std::bind(&Onemoreclass::Print, this);

    _someClass.SomethingChanged  += std::bind<std::string>(&Onemoreclass::Print, this);
    

    hmm.... irgendwie auch nicht ganz richtig.

    Error C2672: 'std::invoke': no matching overloaded function found (1)
    Error C2893: Failed to specialize function template 'unknown-type std::invoke(_Callable &&,_Types &&...) noexcept(<expr>)' (1)


  • Mod

    @booster sagte in c++ 11 Event implementation:

    class Onemoreclass
    {
    SomeClass _someclass{};
    public:
    Onemoreclass()
    {
    _someClass.SomethingChanged += Print;
    }

    private:
    void Print(std::string message) {std::cout << message << endl; }
    }

    Bitte nicht wild herum raten! Wie kommst du darauf, bei bind den template-Parameter auf std::string zu setzen?

    Dein Problem ist, dass du nach wie vor nicht weißt, was du in deinen Eventhandler packen möchtest, oder umgekehrt, welche Art von Events dein Eventhandler handlen soll. Die Funktion Print hat ein Argument vom Typ std::string (neben dem versteckten this durch seinen Status als Memberfunktion), dein Eventhandler erwartet aber eine Funktion ohne Argumente. Also entweder muss dein Eventhandler einen anderen Funktionstyp annehmen, oder du musst dem bind noch sagen, welchen String es als zusätzlichen Parameter noch mit an sein Ergebnis pappen soll. Derzeit ist der Stringparameter noch unbesetzt, daher erzeugt bind ein Funktionsobjekt, das noch einen String als Parameter erwartet. Und dessen Typ passt nicht zu deinem Eventhandler.



  • @SeppJ sagte in c++ 11 Event implementation:

    Bitte nicht wild herum raten!

    Nun ja ich kann jetzt abwarten bis mir jemand eine fertige Lösung präsentiert oder selber mit dazu beitragen eine Lösung zu finden. Da ich die Lösung aber nicht kenne, kann ich ja nur testen.

    @SeppJ sagte in c++ 11 Event implementation:

    Dein Problem ist, dass du nach wie vor nicht weißt, was du in deinen Eventhandler packen möchtest, oder umgekehrt, welche Art von Events dein Eventhandler handlen soll.

    Na doch weiß ich dass.
    Der jenige der das Event auslöst übergibt ja den parameter vom typ String

    Die Auslösung des Events fehlt noch im oberen Beispiel.

    class SomeClass
    {
     public:
           Event<std::string> SomethingChanged;
    
        void SayHello()
       { 
          SomethingChanged("Hello");
       }
    
    }
    

    Wird SayHello aufgerufen wird das Event ausgelöst und der string "Hello" übergeben.

    In der Verwendung meines Lambda ausdruck kann ich ja sehen dass es funktioniert:
    Und ergänze das ganze noch der vollständigkeit um einen Membervariable.

    std::string _name = "Welt";
    
    
    _someClass.SomethingChanged += [this](std::string message) {std::cout << message << " " << _name <<   endl; }
    


  • OK, habe es noch mal ausprobiert: Ideone-Code

    _someClass.SomethingChanged  += std::bind(&Onemoreclass::Print, *this, std::placeholders::_1);
    

    (beachte die Dereferenzierung bei *this)



  • @Th69

    Hallo Th69

    Hast du das kompiliert? Also bei mir kommt ne Fehlermeldung:

    Error C2661: 'std::tuple<Onemoreclass,std::_Ph<1>>::tuple': no overloaded function takes 2 arguments (1)



  • Ja! Hast du den Link nicht angeklickt?

    Welchen Compiler nutzt du denn (das man solche Infos immer explizit nachfragen muß...)?



  • @Th69 sagte in c++ 11 Event implementation:

    das man solche Infos immer explizit nachfragen muß..

    (dass man hier immer solche schnippischen Kommentare erhält)

    IDE: Visual Studio 2017
    Plattform Toolset: v141_xp
    Language: ISO C++17 Standard (/std:c++17)



  • Und eigentlich ging es mir ja da drum dass ich das Event wieder aushängen kann.

    _someClass.SomethingChanged  -= ??
    


  • Wenn beides innerhalb einer Funktion passiert (also quasi temporär), dann sollte es auch so funktionieren:

    auto f = [](std::string message) {std::cout << message << endl; };
    _someClass.SomethingChanged += f;
    // do something ...
    _someClass.SomethingChanged -= f;
    

    Ansonsten poste mal 1:1 die ganze Fehlermeldung vom VS bzgl. meines Codes.



  • Das Problem ist, dass std::function keinen Vergleichsoperator anbietet, bzw. beim Gleichheitsoperator nur prüft, ob der interner Zeiger (oder was auch immer) gültig ist.
    Wenn du wirklich auf Gleichheit prüfen möchtest muss du das Objekt und die aufzurufende Funktion mitführen, um hinterher zwei Callbacks vergleichen zu können. Das geht bei Lambdas allerdings schon nicht mehr, und da ist jetzt die Frage, ob man das jetzt wirklich alles selbst programmieren möchte oder vielleicht nicht doch lieber boost::signals2 benutzt.



  • @Th69 sagte in c++ 11 Event implementation:

    Wenn beides innerhalb einer Funktion passiert (also quasi temporär), dann sollte es auch so funktionieren

    ja das tut es ja eben leider nicht.

    @Th69 sagte in c++ 11 Event implementation:

    Ansonsten poste mal 1:1 die ganze Fehlermeldung vom VS bzgl. meines Codes.

    Hmm. Jetzt stehe ich vor nem anderen Problem. Habe bisher nur in meinem richtigen Projekt getestet.
    Da kommt die Fehlermeldung.

    Für den Test habe ich mir nun ein neues Projekt gemacht. Und deinen Code rein kopiert. Da kommt keine Fehlermeldung.
    Keine Ahnung was an dem Testprojekt anders ist.

    @DocShoe sagte in c++ 11 Event implementation:

    Das geht bei Lambdas allerdings schon nicht mehr, und da ist jetzt die Frage, ob man das jetzt wirklich alles selbst programmieren möchte oder vielleicht nicht doch lieber boost::signals2 benutzt.

    Ok hatte halt eine einfache Implementation für Events gesucht. Sollte doch vieleicht lieber was bewertes verwenden.
    Danke für den Tipp.



  • bekomme genau die selbe Fehlermeldung auch wenn ich signals2 verwende:

    Hier mal die ganze Fehlermeldung aus dem Output:

    communication::CommunicationManager entspricht hier der OnemoreClass

    xutility(332): error C2661: 'std::tuple<communication::CommunicationManager,std::_Ph<1>>::tuple': no overloaded function takes 2 arguments
    functional(1896): note: see reference to function template instantiation 'std::_Compressed_pair<void (__thiscall communication::CommunicationManager::* )(std::string),std::tuple<communication::CommunicationManager,std::_Ph<1>>,false>::_Compressed_pair<_Ty,communication::CommunicationManager&,const std::_Ph<1>&>(std::_One_then_variadic_args_t,_Other1 &&,communication::CommunicationManager &,const std::_Ph<1> &)' being compiled
            with
            [
                _Ty=void (__thiscall communication::CommunicationManager::* )(std::string),
                _Other1=void (__thiscall communication::CommunicationManager::* )(std::string)
            ]
    \functional(1897): note: see reference to function template instantiation 'std::_Compressed_pair<void (__thiscall communication::CommunicationManager::* )(std::string),std::tuple<communication::CommunicationManager,std::_Ph<1>>,false>::_Compressed_pair<_Ty,communication::CommunicationManager&,const std::_Ph<1>&>(std::_One_then_variadic_args_t,_Other1 &&,communication::CommunicationManager &,const std::_Ph<1> &)' being compiled
            with
            [
                _Ty=void (__thiscall communication::CommunicationManager::* )(std::string),
                _Other1=void (__thiscall communication::CommunicationManager::* )(std::string)
            ]
    functional(1896): note: while compiling class template member function 'std::_Binder<std::_Unforced,void (__thiscall communication::CommunicationManager::* )(std::string),communication::CommunicationManager &,const std::_Ph<1> &>::_Binder(_Fx &&,communication::CommunicationManager &,const std::_Ph<1> &)'
            with
            [
                _Fx=void (__thiscall communication::CommunicationManager::* )(std::string)
            ]
    functional(1923): note: see reference to function template instantiation 'std::_Binder<std::_Unforced,void (__thiscall communication::CommunicationManager::* )(std::string),communication::CommunicationManager &,const std::_Ph<1> &>::_Binder(_Fx &&,communication::CommunicationManager &,const std::_Ph<1> &)' being compiled
            with
            [
                _Fx=void (__thiscall communication::CommunicationManager::* )(std::string)
            ]
    \communicationmanager.cpp(188): note: see reference to class template instantiation 'std::_Binder<std::_Unforced,void (__thiscall communication::CommunicationManager::* )(std::string),communication::CommunicationManager &,const std::_Ph<1> &>' being compiled
    

Log in to reply