A container of std::function with polymorphic types as function arguments

我与影子孤独终老i 提交于 2021-02-19 05:22:17

问题


I would like to have "yet another" callback registration stuff. Different event types extending a common base event type will trigger associated callback functions.

here is the initial draft or idea

#include <functional>
#include <iostream>
#include <unordered_map>

class BaseEvent 
{ 
public: 
    virtual ~BaseEvent() {}
};

class DerivedEvent_1 : public BaseEvent {};
class DerivedEvent_2 : public BaseEvent {};


// a container holding callback functions
std::unordered_map<size_t/*event*/, std::function<void(BaseEvent)>/*callback*/> _callbacks;


// register callback funtion associated with specific event
template<typename EVT>  
void registerCallback(std::function<void(EVT)> cb)
{
    std::cout << "store callback associated with event " << typeid(EVT).name() << " [" << typeid(EVT).hash_code() << "]" << std::endl;
    //_functions[ typeid(EVT).hash_code() ] = cb; // FIXME
}


// trigger callback function
void triggerCallback(const BaseEvent* e)
{
    std::cout << "trigger callback with event " << typeid(*e).name() << " [" << typeid(*e).hash_code() << "]" << std::endl;
    //_functions[ typeid(*e).hash_code() ] (*e); // FIXME
}

// callback function for DerivedEvent_1
void callback_1(DerivedEvent_1 event_1)
{
    std::cout << "callback_1 called" << std::endl;
}

// callback function for DerivedEvent_2
void callback_2(DerivedEvent_2 event_2)
{
    std::cout << "callback_2 called" << std::endl;
}


int main(int argc, char *argv[])
{
    registerCallback<DerivedEvent_1>( [](DerivedEvent_1 e) { callback_1(e); } );
    registerCallback<DerivedEvent_2>( [](DerivedEvent_2 e) { callback_2(e); } );


    DerivedEvent_1 e1;
    DerivedEvent_2 e2;

    triggerCallback(&e1);
    triggerCallback(&e2);

    return 1;
}

so far so good without real implementation...

$ g++ -std=c++11 -o testStdFunction testStdFunvtion.cpp 
$ ./testStdFunction 
store callback associated with event 14DerivedEvent_1 [4527193776]
store callback associated with event 14DerivedEvent_2 [4527193680]
trigger callback with event 14DerivedEvent_1 [4527193776]
trigger callback with event 14DerivedEvent_2 [4527193680]

The situations and questions are:

  • Events are class or struct which can have specific attributes as payload
  • I would like to keep callback functions (e.g. void callback_1(DerivedEvent_1 event_1) without pointer as argument. Reason: those callback functions may already exist in code base and I would like to not change it or make extra wrapper.
  • how can I let _callbacks map to have std::function with different signatures? i.e. to let the _callbacks map can hold std::function

The intention is to fix the FIXME code in registerCallback and triggerCallback. so they will look like this after running the code

$ g++ -std=c++11 -o testStdFunction testStdFunvtion.cpp 
$ ./testStdFunction 
store callback associated with event 14DerivedEvent_1 [4527193776]
store callback associated with event 14DerivedEvent_2 [4527193680]
trigger callback with event 14DerivedEvent_1 [4527193776]
callback_1 called
trigger callback with event 14DerivedEvent_2 [4527193680]
callback_2 called

回答1:


You can use an erased wrapper.
The following code prints exactly the messages the OP posted in the question.
Key classes are BaseWrapper and the template class Wrapper.
Moreover, I slightly changed function signatures all around the code to let it work correctly.

#include <functional>
#include <iostream>
#include <unordered_map>
#include<memory>
#include<utility>

class BaseEvent 
{ 
public: 
    virtual ~BaseEvent() {}
};

class DerivedEvent_1 : public BaseEvent {};
class DerivedEvent_2 : public BaseEvent {};

struct BaseWrapper {
    virtual void operator()(const BaseEvent *) = 0;
};

template<typename T>
struct Wrapper: BaseWrapper {
    std::function<void(T)> fn;
    void operator()(const BaseEvent *e) {
        fn(*static_cast<const T*>(e));
    }
};


// a container holding callback functions
std::unordered_map<size_t/*event*/, std::unique_ptr<BaseWrapper>/*callback*/> _functions;


// register callback funtion associated with specific event
template<typename EVT>  
void registerCallback(std::function<void(const EVT &)> cb)
{
    std::cout << "store callback associated with event " << typeid(EVT).name() << " [" << typeid(EVT).hash_code() << "]" << std::endl;
    auto w = std::make_unique<Wrapper<EVT>>();
    w->fn = cb;
    _functions[ typeid(EVT).hash_code() ] = std::move(w);
}


// trigger callback function
void triggerCallback(const BaseEvent* e)
{
    std::cout << "trigger callback with event " << typeid(*e).name() << " [" << typeid(*e).hash_code() << "]" << std::endl;
    (*_functions[ typeid(*e).hash_code() ] )(e);
}

// callback function for DerivedEvent_1
void callback_1(const DerivedEvent_1 &event_1)
{
    std::cout << "callback_1 called" << std::endl;
}

// callback function for DerivedEvent_2
void callback_2(const DerivedEvent_2 &event_2)
{
    std::cout << "callback_2 called" << std::endl;
}

int main(int argc, char *argv[])
{
    registerCallback<DerivedEvent_1>( [](DerivedEvent_1 e) { callback_1(e); } );
    registerCallback<DerivedEvent_2>( [](DerivedEvent_2 e) { callback_2(e); } );

    DerivedEvent_1 e1;
    DerivedEvent_2 e2;

    triggerCallback(&e1);
    triggerCallback(&e2);

    return 1;
}

The wrapper can be improved in terms of performance (as an example, using static member methods instead of polymorphism, left to the reader as an exercise).
The basic idea behind the solution is the so called type-erasure.
Google will help you in finding further details about that.



来源:https://stackoverflow.com/questions/37904897/a-container-of-stdfunction-with-polymorphic-types-as-function-arguments

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!