【问题标题】:Storing boost::function objects in a container在容器中存储 boost::function 对象
【发布时间】:2012-10-26 21:30:53
【问题描述】:

我有一个KeyCallbacks 的向量:

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

我用它来存储按下键盘按钮时的所有侦听器。我可以使用for_each 添加它们并将事件分派给所有回调,但我不知道如何从我的向量中实际删除特定的KeyCallback 签名。

例如我想要这样的东西:

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

根据boost::function 文档(请参阅here),没有比较函数对象之类的东西,这可以解释我的上述问题。那我卡住了吗?有什么好的办法吗?

(我读到了关于回调机制的 boost::signals,但它显然很慢,而且我预计回调可能每帧被触发几次。)

【问题讨论】:

    标签: c++ boost boost-function


    【解决方案1】:

    方法#1:

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

    函数对象包装器可以通过 == 或 != 与任何可以存储在包装器中的函数对象进行比较。

    因此,解决方案之一是为 UnregisterCallback 的参数定义特殊类型(也支持类型擦除)。这是基于事实,您可以将 boost::function 与 functor/function 进行比较 - 结果您仍然会有 boost::function 的向量,仅在需要执行比较的地方才需要新类型,例如取消注册回调:

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

    输出是:

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

    优点

    • 我们仍然使用 boost::function 在向量中注册和存储
    • 只有在需要将其传递给 unregister_callback 时,Functor 对象才应该定义比较
    • 它可以很容易地泛化 - 只需添加一个模板参数,而不是使用 typedefed KeyCallback。因此,可以在其他地方轻松使用,用于其他类型的回调。

    缺点

    • 如果用户已经将回调包装到 boost::function - 它不能与 unregister_callback 一起使用,因为它需要一些可以与 boost::function 进行比较的东西(例如,函数指针,或具有定义比较的函子)李>


    方法#2:

    另一种方法是实现自定义 boost::function-like 解决方案,它接受仅可比较的回调。

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

    输出是:

    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
    

    优点

    • 我们在注册/注销回调中使用相同的类型。用户可以将他的函数和仿函数存储在外部包装到 KeyCallback - 并将 KeyCallback 传递给我们的 unregister_callback。
    • 不依赖于 boost::function

    缺点

    • Functor 对象必须已定义比较,即使它不与 unregister_callback 一起使用
    • 如果用户已经将回调包装到 boost::function - 它不能转换为我们的 KeyCallback,因为它需要定义比较。
    • 如果您在其他地方需要类似的功能,使用不同类型的回调 - 那么我们的 boost::function-like 类应该改进(采用不同的多个参数等),或者我们可以提取和修改 boost::函数本身。


    方法#3:

    这里我们正在创建一个继承自 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();
    }
    

    输出是:

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

    优点

    • 我们可以在注册/注销回调中使用相同的类型。用户可以将他的函数和仿函数存储在外包装到 KeyCallback - 并将 KeyCallback 传递给我们的 unregister_callback。此外,在这个版本中,我们可以使用普通的 boost::function 作为注册函数的参数。
    • 我们仍然可以使用 boost::function 在向量中注册和存储
    • 当我们使用 boost::function 进行注册时,functor 对象应该只有在需要传递给 unregister_callback 时才定义比较。
    • 它是通用的 - 因此,可以在其他地方轻松使用,用于其他类型的回调。
    • 此版本基于普通函数指针而不是分配+抽象类 (vptr)。因此,它少了一个粗心大意,更易于管理。

    缺点

    • 如果用户已经将回调包装到 boost::function - 它不能与 unregister_callback 一起使用,因为它需要一些可以与 boost::function 进行比较的东西(例如,函数指针,或具有定义比较的函子)李>


    编辑:

    太棒了,我现在正在尝试#1,但我不太明白为什么当我们应用自己的 == 运算符时它会起作用?

    boost::function can be compared 针对函数或仿函数,但不针对另一个 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;
    }
    

    在我们的#1 方法中,我们进行类似于“bf1 == f1;”的比较。 KeyCallbackChecker 捕获函子/函数并在 ConcreteCallback::equals 中执行此类比较。

    【讨论】:

    • 太棒了,我现在正在尝试 #1,但我不太明白为什么当我们应用自己的 == 运算符时它会起作用?
    • 我已经在底部添加了答案。我还补充了这两种方法的优缺点。
    【解决方案2】:

    我不确定我是否正确理解了您的问题。我的理解是你有一个KeyCallback 函数对象的向量。他们每个人都有相同的签名void (const KeyEvent &amp;)。通常,您为某个KeyEvent 对象调用它们中的每一个。并且您想从向量中删除这些对象中的一个或多个。

    如果我是对的,那么实际的问题是你如何识别某个KeyCallback 对象?这是一种解决方案:使用指针。

    如果KeyCallback 对象是函数指针,则没有两个函数可以具有相同的地址。如果是operator()的类对象,那么每个具体对象都有一个唯一的地址。因此以下代码有效。但它不再使用向量,而是使用map。而且您需要使用for 循环而不是for_each

    #include <iostream>
    #include <map>
    #include <boost/function.hpp>
    
    typedef int KeyEvent;
    typedef boost::function<void (const KeyEvent &)> KeyCallback;
    
    void func1 (const KeyEvent &x)
    {
        std::cout << "Call func1 with " << x << std::endl;
    }
    
    void func2 (const KeyEvent &x)
    {
        std::cout << "Call func2 with " << x << std::endl;
    }
    
    class func3
    {
        public :
    
        void operator() (const KeyEvent &x) const
        {
            std::cout << "Call func3 with " << x << std::endl;
        }
    };
    
    class func4
    {
        public :
    
        func4 () : data_(0) {}
        func4 (int d) : data_(d) {}
    
        void operator() (const KeyEvent &x) const
        {
            std::cout << "Call func4(" << data_ << ") with " << x << std::endl;
        }
    
        private :
    
        int data_;
    };
    
    template <typename T>
    long ptol (T *p)
    {
        void *vp = (void *) p;
        return reinterpret_cast<long>(vp);
    }
    
    int main()
    {
        func3 f30;
        func4 f40;
        func4 f41(1);
        func4 f42(2);
        std::map<long, KeyCallback> callback;
        callback[ptol(&func1)] = func1;
        callback[ptol(&func2)] = func2;
        callback[ptol(&f30)] = f30;
        callback[ptol(&f40)] = f40;
        callback[ptol(&f41)] = f41;
        callback[ptol(&f42)] = f42;
    
        for (std::map<long, KeyCallback>::const_iterator m = callback.begin(); m != callback.end(); ++m)
            m->second(1);
    
        std::cout << "ERASE func1 and f41" << std::endl;
    
        callback.erase(ptol(&func1));
        callback.erase(ptol(&f41));
    
        for (std::map<long, KeyCallback>::const_iterator m = callback.begin(); m != callback.end(); ++m)
            m->second(1);
    
        return 0;
    }
    

    这是输出

    Call func1 with 1
    Call func2 with 1
    Call func4(2) with 1
    Call func4(1) with 1
    Call func4(0) with 1
    Call func3 with 1
    ERASE func1 and f41
    Call func2 with 1
    Call func4(2) with 1
    Call func4(0) with 1
    Call func3 with 1
    

    缺点是

    • 您不能按特定顺序调用它们。
    • f40,...,f42 等对象必须在插入之外创建。您不能使用insert 即时将函数对象插入map,例如, callback.insert(std::make_pair(address, func4(5))); 除非您自己分配和管理一个唯一的address,因为您无法获取临时对象的地址。
    • 这个示例工作是所有f40f42 都有唯一的地址。我们添加它们时它们都存在,因此它们不能有重复的地址。但是如果说我动态创建了f42,添加它,delete 它,创建另一个func4 对象,然后添加它。可能是新的func4 对象使用了旧的f42 的内存位置,因此地址不再唯一,新的插入将替换新的。

    但是,我认为您可以为每个回调提供比其地址更好的唯一密钥。

    【讨论】:

    • boost::function 的主要属性是类型擦除。他的代码只收到了 boost::function,没有任何关于 funciton/functor 本身的信息。看看 UnregisterCallback 签名: void UnregisterCallback(KeyCallback callback) 。你建议如何在 UnregisterCallback 的参数上调用 ptol?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-12-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多