I have a vector of KeyCallback
s:
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.)
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:
#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
- Functor object should have defined comparison only when there is need to pass it to unregister_callback
- It can be easly generalized - just add one template parameter instead of using 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 (e.g. function pointer, or functor with defined comparison)
Approach #2:
Another approach is to implement custom boost::function-like solution, which accepts comparable-only callbacks.
#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.
- There is no dependency on boost::function
Cons:
- Functor object must have defined comparison even if it is not used with unregister_callback
- If user already has callback wrapped to boost::function - it can't be converted to our KeyCallback, becuase it requires defined comparison.
- 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.
Approach #3:
Here we are creating new class which is inherited from std/boost ::function
#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. Morever in this version we may use plain boost::function for parameter of register function.
- We still can use boost::function for registring and storing in 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.
- 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). 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 (e.g. function pointer, or functor with defined comparison)
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.
I am not sure if I understand your problem correctly. My understanding is that you have a vector of KeyCallback
function objects. Each of them has the same signature void (const KeyEvent &)
. Normally you call each of them for a certain KeyEvent
object. 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? Here is one solution: use pointer.
If a KeyCallback
object is a function pointer, then no two function can have the same address. If it is a class object with operator()
, then each concrete object has a unique address. Therefore the following code works. But it no longer use a vector but a map
. And you need to use a for
loop instead of 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. You cannot useinsert
to insert a function object into themap
on the fly, like, callback.insert(std::make_pair(address, func4(5))); unless you assign and manage a uniqueaddress
yourself, since you cannot take address of an temporary object. - This example work is that all
f40
tof42
have unique address. They all exist when we add them, so they cannot have duplicate address. But if say I dynamically createdf42
, add it,delete
it, create anotherfunc4
object, then add it. It is possible that the newfunc4
object used the oldf42
's memory location, and therefore the address is no longer unique and the new insertion will replace the new one.
However I think you can come up with a better unique key for each call back than their addresses.
来源:https://stackoverflow.com/questions/13094720/storing-boostfunction-objects-in-a-container