简体   繁体   English

在容器中存储boost :: function对象

[英]Storing boost::function objects in a container

I have a vector of KeyCallback s: 我有一个KeyCallback的向量:

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_each将事件分派给所有回调,但我不知道如何从我的向量中实际删除特定的KeyCallback签名。

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. 根据boost::function文档(见这里 ),没有比较函数对象的东西,这可以解释我上面的问题。 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.) (我读到了关于回调机制的boost::signals ,但它显然非常慢,而且我希望回调可能会在几帧内被触发几次。)

Approach #1: 方法#1:

http://www.boost.org/doc/libs/1_51_0/doc/html/function/tutorial.html#id1546064 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). 因此,其中一个解决方案是为UnregisterCallback的参数定义特殊类型(它也支持类型擦除)。 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, eg UnregisterCallback: 这是基于事实,你可以比较boost :: function和functor / function - 结果你仍然会有boost :: function的向量,只有你需要执行比较的地方才需要新类型,例如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 : 优点

  • We still use boost::function for registring and storing in vector 我们仍然使用boost :: function进行registring并存储在vector中
  • Functor object should have defined comparison only when there is need to pass it to unregister_callback Functor对象只有在需要将其传递给unregister_callback时才应定义比较
  • It can be easly generalized - just add one template parameter instead of using typedefed KeyCallback. 它可以很容易地推广 - 只需添加一个模板参数,而不是使用typedefed KeyCallback。 So, can be easily used at other places, for other types of callbacks. 因此,可以在其他地方轻松使用,用于其他类型的回调。

Cons : 缺点

  • If user already has callback wrapped to boost::function - it can't be used with unregister_callback, because it requires something that can be compared to boost::function (eg function pointer, or functor with defined comparison) 如果用户已将回调包装到boost :: function - 它不能与unregister_callback一起使用,因为它需要一些可以与boost :: function进行比较的东西(例如函数指针或带有定义比较的函子)


Approach #2: 方法#2:

Another approach is to implement custom boost::function-like solution, which accepts comparable-only callbacks. 另一种方法是实现自定义的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;
}

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 : 优点

  • We use same type in register/unregister callback. 我们在注册/取消注册回调中使用相同的类型。 User may store his functions and functors outside wrapped to KeyCallback - and pass KeyCallback to our unregister_callback. 用户可以将他的函数和函子存储在包装到KeyCallback之外 - 并将KeyCallback传递给我们的unregister_callback。
  • There is no dependency on boost::function 对boost :: function没有依赖性

Cons : 缺点

  • Functor object must have defined comparison even if it is not used with unregister_callback Functor对象必须具有已定义的比较,即使它未与unregister_callback一起使用
  • If user already has callback wrapped to boost::function - it can't be converted to our KeyCallback, becuase it requires defined comparison. 如果用户已将回调包装到boost :: function - 它无法转换为我们的KeyCallback,因为它需要定义比较。
  • If you need similar functionality at other places, with different kind of callbacks - then our boost::function-like classes should be improved (taking different and several parameters, etc, etc) or we may extract and modify boost::funciton itself. 如果你需要在其他地方使用类似的功能,使用不同类型的回调 - 那么我们的boost :: function-like类应该被改进(使用不同的和几个参数等等),或者我们可以提取和修改boost :: funciton本身。


Approach #3: 方法#3:

Here we are creating new class which is inherited from std/boost ::function 这里我们创建一个继承自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 : 优点

  • We can use same type in register/unregister callback. 我们可以在注册/取消注册回调中使用相同的类型。 User may store his functions and functors outside wrapped to KeyCallback - and pass KeyCallback to our unregister_callback. 用户可以将他的函数和函子存储在包装到KeyCallback之外 - 并将KeyCallback传递给我们的unregister_callback。 Morever in this version we may use plain boost::function for parameter of register function. 在这个版本中,我们可以使用普通的boost :: function作为寄存器功能的参数。
  • We still can use boost::function for registring and storing in vector 我们仍然可以将boost :: function用于registring并存储在vector中
  • When we use boost::function for registering, functor object should have defined comparison only when there is need to pass it to unregister_callback. 当我们使用boost :: function进行注册时,functor对象应该只在需要将它传递给unregister_callback时才定义比较。
  • It is generalized - so, can be easily used at other places, for other types of callbacks. 它是通用的 - 因此,可以很容易地在其他地方使用,用于其他类型的回调。
  • This version is based on plain function pointer instead of allocation+abstract class (vptr). 此版本基于普通函数指针而不是分配+抽象类(vptr)。 So it has one less inderection, it easier to manage. 所以它有一个较少的无效,更容易管理。

Cons : 缺点

  • If user already has callback wrapped to boost::function - it can't be used with unregister_callback, because it requires something that can be compared to boost::function (eg function pointer, or functor with defined comparison) 如果用户已将回调包装到boost :: function - 它不能与unregister_callback一起使用,因为它需要一些可以与boost :: function进行比较的东西(例如函数指针或带有定义比较的函子)


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? 太棒了,我现在正在尝试#1,但我不太明白为什么它应用我们自己的==运算符时有效?

boost::function can be compared against functions or functors, but not against another boost::function: boost :: function 可以与函数或函子进行比较 ,但不能与另一个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;". 在我们的#1方法中,我们进行类似于“bf1 == f1;”的比较。 KeyCallbackChecker captures functor/function and performs such kind of comparison inside ConcreteCallback::equals. KeyCallbackChecker捕获函子/函数并在ConcreteCallback :: equals中执行此类比较。

I am not sure if I understand your problem correctly. 我不确定我是否正确理解你的问题。 My understanding is that you have a vector of KeyCallback function objects. 我的理解是你有一个KeyCallback函数对象的向量。 Each of them has the same signature void (const KeyEvent &) . 它们中的每一个都具有相同的签名void (const KeyEvent &) Normally you call each of them for a certain KeyEvent object. 通常,您为每个KeyEvent对象调用它们。 And you want to delete one or more of these objects form the vector. 并且您希望从向量中删除这些对象中的一个或多个。

If I am right, then the actual problem is that how do you identify a certain KeyCallback object? 如果我是对的,那么实际的问题是你如何识别某个KeyCallback对象? Here is one solution: use pointer. 这是一个解决方案:使用指针。

If a KeyCallback object is a function pointer, then no two function can have the same address. 如果KeyCallback对象是函数指针,那么没有两个函数可以具有相同的地址。 If it is a class object with operator() , then each concrete object has a unique address. 如果它是一个带有operator()的类对象,那么每个具体对象都有一个唯一的地址。 Therefore the following code works. 因此,以下代码有效。 But it no longer use a vector but a map . 但它不再使用矢量而是使用map And you need to use a for loop instead of for_each . 而且你需要使用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;
}

And here is output 这是输出

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

The downside is 缺点是

  • You cannot call them by a specific order. 您无法通过特定订单呼叫它们。
  • objects like f40 ,..., f42 has to be created outside the insertion. 必须在插入之外创建像f40 ,..., f42这样的对象。 You cannot use insert to insert a function object into the map on the fly, like, callback.insert(std::make_pair(address, func4(5))); 您不能使用insert将函数对象动态插入到map ,例如,callback.insert(std :: make_pair(address,func4(5))); unless you assign and manage a unique address yourself, since you cannot take address of an temporary object. 除非您自己分配和管理唯一address ,否则您无法获取临时对象的地址。
  • This example work is that all f40 to f42 have unique address. 这个示例工作是所有f40f42都有唯一的地址。 They all exist when we add them, so they cannot have duplicate address. 当我们添加它们时它们都存在,因此它们不能具有重复的地址。 But if say I dynamically created f42 , add it, delete it, create another func4 object, then add it. 但是如果说我动态创建f42 ,添加它, delete它,创建另一个func4对象,然后添加它。 It is possible that the new func4 object used the old f42 's memory location, and therefore the address is no longer unique and the new insertion will replace the new one. 新的func4对象可能使用旧的f42的内存位置,因此地址不再是唯一的,新插入将替换新的插入。

However I think you can come up with a better unique key for each call back than their addresses. 但是我认为你可以为每个回调提供一个比他们的地址更好的唯一密钥。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM