c++boostboost-function

Storing boost::function objects in a container


I have a vector of KeyCallbacks:

typedef boost::function<void (const KeyEvent&)> KeyCallback

which I use to store all listeners for when a keyboard button is pressed. I can add them and dispatch the events to all callbacks with for_each, but I do not know how to actually erase a specific KeyCallback signature from my vector.

For example I want something like this:

void InputManager::UnregisterCallback(KeyCallback callback) {
  mKeyCallbacks.erase(std::find(mKeyCallbacks.begin(), mKeyCallbacks.end(), callback));
}

According to boost::function documentation (see here), there is no such thing as comparing function objects, which would explain my problems with the above. So am I stuck? Is there any nice way around this?

(I read about boost::signals for callback mechanisms, but it's apparently quite slow, and I expect callbacks to be fired possibly several times a frame.)


Solution

  • Approach #1:

    http://www.boost.org/doc/libs/1_51_0/doc/html/function/tutorial.html#id1546064

    Function object wrappers can be compared via == or != against any function object that can be stored within the wrapper.

    So, one of solutions, is to define special type for UnregisterCallback's parameter (which also supports type erasure). That is based on fact, that you can compare boost::function with functor/function - as the result you still will have vector of boost::function, new type is required only for places where you need to perform comparison, e.g. UnregisterCallback:

    LIVE DEMO

    #include <boost/scoped_ptr.hpp>
    #include <boost/function.hpp>
    #include <algorithm>
    #include <iostream>
    #include <typeinfo>
    #include <ostream>
    #include <vector>
    #include <string>
    
    using namespace std;
    using namespace boost;
    
    typedef int KeyEvent;
    typedef function<void (const KeyEvent &)> KeyCallback;
    
    struct AbstractCallback
    {
        virtual bool equals(const KeyCallback &f) const=0;
        virtual ~AbstractCallback(){}
    };
    
    template<typename Callback>
    struct ConcreteCallback : AbstractCallback
    {
        const Callback &callback;
        explicit ConcreteCallback(const Callback &p_callback) : callback(p_callback) {}
        virtual bool equals(const KeyCallback &f) const
        {
            return callback == f;
        }
    };
    
    struct KeyCallbackChecker
    {
        scoped_ptr<AbstractCallback> func;
    public:
        template<typename Func>
        KeyCallbackChecker(const Func &f) : func(new ConcreteCallback<Func>(f)) {}
        friend bool operator==(const KeyCallback &lhs,const KeyCallbackChecker &rhs)
        {
            return rhs.func->equals(lhs);
        }
        friend bool operator==(const KeyCallbackChecker &lhs,const KeyCallback &rhs)
        {
            return rhs==lhs;
        }
    };
    
    void func1(const KeyEvent &)
    {
        cout << "func1" << endl;
    }
    
    void func3(const KeyEvent &)
    {
        cout << "func3" << endl;
    }
    
    class func2
    {
        int data;
    public:
        explicit func2(int n) : data(n) {}
        friend bool operator==(const func2 &lhs,const func2 &rhs)
        {
            return lhs.data==rhs.data;
        }
        void operator()(const KeyEvent &)
        {
            cout << "func2, data=" << data << endl;
        }
    };
    
    struct Caller
    {
        template<typename F> void operator()(F f)
        {
            f(1);
        }
    };
    
    class Callbacks
    {
        vector<KeyCallback> v;
    public:
        void register_callback(const KeyCallback &callback)
        {
            v.push_back(callback);
        }
        void unregister_callback(const KeyCallbackChecker &callback)
        {
            vector<KeyCallback>::iterator it=find(v.begin(),v.end(),callback);
            if(it!=v.end())
                v.erase(it);
        }
        void call_all()
        {
            for_each(v.begin(),v.end(),Caller());
            cout << string(16,'_') << endl;
        }
    };
    
    int main(int argc,char *argv[])
    {
        Callbacks cb;
        cb.register_callback(func1);
        cb.register_callback(func2(1));
        cb.register_callback(func2(2));
        cb.register_callback(func3);
        cb.call_all();
    
        cb.unregister_callback(func2(2));
        cb.call_all();
        cb.unregister_callback(func1);
        cb.call_all();
    
    return 0;
    }
    

    Output is:

    func1
    func2, data=1
    func2, data=2
    func3
    ________________
    func1
    func2, data=1
    func3
    ________________
    func2, data=1
    func3
    ________________
    

    Pros:

    Cons:



    Approach #2:

    Another approach is to implement custom boost::function-like solution, which accepts comparable-only callbacks.

    LIVE DEMO

    #include <boost/shared_ptr.hpp>
    #include <algorithm>
    #include <iostream>
    #include <typeinfo>
    #include <ostream>
    #include <vector>
    
    using namespace std;
    using namespace boost;
    
    typedef int KeyEvent;
    typedef void (*func_type)(const KeyEvent &);
    
    struct AbstractCallback
    {
        virtual void operator()(const KeyEvent &p)=0;
        virtual bool compare_to(const std::type_info &rhs_type,const void *rhs) const=0;
        virtual bool compare_to(const std::type_info &rhs_type,const func_type *rhs) const=0;
        virtual bool equals(const AbstractCallback &rhs) const=0;
    };
    
    template<typename Callback>
    struct ConcreteCallback : AbstractCallback
    {
        Callback callback;
        ConcreteCallback(Callback p_callback) : callback(p_callback) {}
        void operator()(const KeyEvent &p)
        {
            callback(p);
        }
        bool compare_to(const std::type_info &rhs_type,const void *rhs) const
        {
            return (typeid(Callback)==rhs_type) &&
                ( *static_cast<const Callback*>(rhs) == callback );
        }
        bool compare_to(const std::type_info &rhs_type,const func_type *rhs) const
        {
            return false;
        }
        bool equals(const AbstractCallback &rhs) const
        {
            return rhs.compare_to(typeid(Callback),&callback);
        }
    };
    
    template<>
    struct ConcreteCallback<func_type> : AbstractCallback
    {
        func_type callback;
        ConcreteCallback(func_type p_callback) : callback(p_callback) {}
        void operator()(const KeyEvent &p)
        {
            callback(p);
        }
        bool compare_to(const std::type_info &rhs_type,const void *rhs) const
        {
            return false;
        }
        bool compare_to(const std::type_info &rhs_type,const func_type *rhs) const
        {
            return *rhs == callback;
        }
        bool equals(const AbstractCallback &rhs) const
        {
            return rhs.compare_to(typeid(func_type),&callback);
        }
    };
    
    
    struct KeyCallback
    {
        shared_ptr<AbstractCallback> func;
    public:
        template<typename Func>
        KeyCallback(Func f) : func(new ConcreteCallback<Func>(f)) {}
        friend bool operator==(const KeyCallback &lhs,const KeyCallback &rhs)
        {
            return lhs.func->equals(*rhs.func);
        }
        void operator()(const KeyEvent &p)
        {
            (*func)(p);
        }
    };
    
    void func1(const KeyEvent &)
    {
        cout << "func1" << endl;
    }
    
    void func3(const KeyEvent &)
    {
        cout << "func3" << endl;
    }
    
    class func2
    {
        int data;
    public:
        func2(int n) : data(n) {}
        friend bool operator==(const func2 &lhs,const func2 &rhs)
        {
            return lhs.data==rhs.data;
        }
        void operator()(const KeyEvent &)
        {
            cout << "func2, data=" << data << endl;
        }
    };
    
    struct Caller
    {
        template<typename F>
        void operator()(F f)
        {
            f(1);
        }
    };
    
    int main(int argc,char *argv[])
    {
        vector<KeyCallback> v;
    
        v.push_back(KeyCallback(func1));
        v.push_back(KeyCallback(func1));
        v.push_back(KeyCallback(func1));
    
        v.push_back(KeyCallback(func2(1)));
        v.push_back(KeyCallback(func2(1)));
    
        v.push_back(KeyCallback(func2(2)));
        v.push_back(KeyCallback(func2(2)));
        v.push_back(KeyCallback(func2(2)));
        v.push_back(KeyCallback(func2(2)));
    
        v.push_back(KeyCallback(func3));
    
        for_each(v.begin(),v.end(),Caller());
    
        cout << count(v.begin(),v.end(),KeyCallback(func1)) << endl;
        cout << count(v.begin(),v.end(),KeyCallback(func2(1))) << endl;
        cout << count(v.begin(),v.end(),KeyCallback(func2(2))) << endl;
        cout << count(v.begin(),v.end(),KeyCallback(func3)) << endl;
        return 0;
    }
    

    Output is:

    func1
    func1
    func1
    func2, data=1
    func2, data=1
    func2, data=2
    func2, data=2
    func2, data=2
    func2, data=2
    func3
    3
    2
    4
    1
    

    Pros:

    Cons:



    Approach #3:

    Here we are creating new class which is inherited from std/boost ::function

    LIVE DEMO

    #include <type_traits>
    #include <functional>
    #include <algorithm>
    #include <stdexcept>
    #include <iostream>
    #include <typeinfo>
    #include <utility>
    #include <ostream>
    #include <vector>
    #include <string>
    
    using namespace std;
    
    // _____________________________Implementation__________________________________________
    
    #define USE_VARIADIC_TEMPLATES 0
    
    template<typename Callback,typename Function>
    bool func_compare(const Function &lhs,const Function &rhs)
    {
        typedef typename conditional<is_function<Callback>::value,typename add_pointer<Callback>::type,Callback>::type request_type;
        if (const request_type* lhs_internal = lhs.template target<request_type>())
            if (const request_type* rhs_internal = rhs.template target<request_type>())
                return *rhs_internal == *lhs_internal;
        return false;
    }
    
    #if USE_VARIADIC_TEMPLATES
        #define FUNC_SIG_TYPES typename ...Args
        #define FUNC_SIG_TYPES_PASS Args...
    #else
        #define FUNC_SIG_TYPES typename function_signature
        #define FUNC_SIG_TYPES_PASS function_signature
    #endif
    
    template<FUNC_SIG_TYPES>
    struct function_comparable: function<FUNC_SIG_TYPES_PASS>
    {
        typedef function<FUNC_SIG_TYPES_PASS> Function;
        bool (*type_holder)(const Function &,const Function &);
    public:
        function_comparable(){}
        template<typename Func>
        function_comparable(Func f_)
            : Function(f_), type_holder(func_compare<Func,Function>)
        {
        }
        template<typename Func>
        function_comparable &operator=(Func f_)
        {
            Function::operator=(f_);
            type_holder=func_compare<Func,Function>;
            return *this;
        }
        friend bool operator==(const Function &lhs,const function_comparable &rhs)
        {
            return rhs.type_holder(lhs,rhs);
        }
        friend bool operator==(const function_comparable &lhs,const Function &rhs)
        {
            return rhs==lhs;
        }
        friend void swap(function_comparable &lhs,function_comparable &rhs)// noexcept
        {
            lhs.swap(rhs);
            lhs.type_holder.swap(rhs.type_holder);
        }
    };
    
    // ________________________________Example______________________________________________
    
    typedef void (function_signature)();
    
    void func1()
    {
        cout << "func1" << endl;
    }
    
    void func3()
    {
        cout << "func3" << endl;
    }
    
    class func2
    {
        int data;
    public:
        explicit func2(int n) : data(n) {}
        friend bool operator==(const func2 &lhs,const func2 &rhs)
        {
            return lhs.data==rhs.data;
        }
        void operator()()
        {
            cout << "func2, data=" << data << endl;
        }
    };
    struct Caller
    {
        template<typename Func>
        void operator()(Func f)
        {
            f();
        }
    };
    class Callbacks
    {
        vector<function<function_signature>> v;
    public:
        void register_callback_comparator(function_comparable<function_signature> callback)
        {
            v.push_back(callback);
        }
        void register_callback(function<function_signature> callback)
        {
            v.push_back(callback);
        }
        void unregister_callback(function_comparable<function_signature> callback)
        {
            auto it=find(v.begin(),v.end(),callback);
            if(it!=v.end())
                v.erase(it);
            else
                throw runtime_error("not found");
        }
        void call_all()
        {
            for_each(v.begin(),v.end(),Caller());
            cout << string(16,'_') << endl;
        }
    };
    
    int main()
    {
        Callbacks cb;
        function_comparable<function_signature> f;
        f=func1;
        cb.register_callback_comparator(f);
    
        cb.register_callback(func2(1));
        cb.register_callback(func2(2));
        cb.register_callback(func3);
        cb.call_all();
    
        cb.unregister_callback(func2(2));
        cb.call_all();
        cb.unregister_callback(func1);
        cb.call_all();
    }
    

    Output is:

    func1
    func2, data=1
    func2, data=2
    func3
    ________________
    func1
    func2, data=1
    func3
    ________________
    func2, data=1
    func3
    ________________
    

    Pros:

    Cons:



    EDIT:

    Awesome, I'm trying it out #1 right now, but I do not quite understand why it works when we apply our own == operators?

    boost::function can be compared against functions or functors, but not against another boost::function:

    #include <boost/function.hpp>
    
    void f1(){}
    void f2(){}
    
    int main()
    {
        boost::function<void ()> bf1(f1),bf2(f2);
        bf1 == f1; // Works OK
        //bf1 == bf2; - COMPILE ERROR
        return 0;
    }
    

    In our #1 approach, we do comparison similar to "bf1 == f1;". KeyCallbackChecker captures functor/function and performs such kind of comparison inside ConcreteCallback::equals.