I'm trying to write an event system for my game. The callbacks that my event manager will store can be both plain functions as well as functors. I also need to be able to compare functions/functors so I know which one I need to disconnect from the event manager.
• Initially I tried using boost::function; it handles functions and functors perfectly well, except it has no operator==, so I can't remove callbacks if I want to.
class EventManager
{
typedef boost::function<void (boost::weak_ptr<Event>)> Callback;
std::map<Event::Type, std::vector<Callback>> eventHandlerMap_;
};
• I also tried using boost::signal, but that also gives me a compilation problem related to operator==:
开发者_如何学Gobinary '==' : no operator found which takes a left-hand operand of type 'const Functor' (or there is no acceptable conversion)
void test(int c) {
std::cout << "test(" << c << ")";
}
struct Functor
{
void operator()(int g) {
std::cout << "Functor::operator(" << g << ")";
}
};
int main()
{
boost::signal<void (int)> sig;
Functor f;
sig.connect(test);
sig.connect(f);
sig(7);
sig.disconnect(f); // Error
}
Any other suggestions about how I might implement this? Or maybe how I can make either boost:: function or boost::signal work? (I'd rather use boost:: function though, since I've heard signal is rather slow for small collections of items.)
Edit: This is the interface of that I'd like EventManager to have.
class EventManager
{
public:
void addEventHandler(Event::Type evType, Callback func);
void removeEventHandler(Event::Type evType, Callback func);
void queueEvent(boost::shared_ptr<Event> ev);
void dispatchNextEvent();
};
You'll find that most generic function wrappers do not support function equality.
Why is this? Well, just look at your functor there:
struct Functor
{
void operator()(int g) {
std::cout << "Functor::operator(" << g << ")";
}
};
This Functor
has no operator==
, and therefore cannot be compared for equality. So when you pass it to boost::signal
by value, a new instance is created; this will compare false for pointer-equality, and has no operator to test for value-equality.
Most functors don't, in fact, have value-equality predicates. It's not useful very much. The usual way to deal with this is to have a handle to the callback instead; boost::signals does this with its connection
object. For example, take a look at this example from the documentation:
boost::signals::connection c = sig.connect(HelloWorld());
if (c.connected()) {
// c is still connected to the signal
sig(); // Prints "Hello, World!"
}
c.disconnect(); // Disconnect the HelloWorld object
assert(!c.connected()); c isn't connected any more
sig(); // Does nothing: there are no connected slots
With this, HelloWorld
doesn't need to have an operator==
, as you're referring directly to the signal registration.
Have you ever tried libsigc and libsigc++? I started using them in linux and fell in love with them. I now use them in my Windows applications as well. I believe it is more extensible and flexible than boost. It is also a breeze to implement.
I highly recommend you consider Don Clugston's "Member Function Pointers and the Fastest Possible C++ Delegates". You can find the article and download the code from here:
http://www.codeproject.com/KB/cpp/FastDelegate.aspx
Among many other benefits, his delegates provide comparison operators (==, !=, <) out of the box. I'm currently using them for a realtime system and find them excellent in every way. I do seem to recall we had to make a minor modification to fix a compiler portability issue; but, that experience will vary based on platform etc.
Also, the article is several years old so you may want to google around for updated code/discussion regarding this delegate implementation if you run into any problems.
No matter, I found the solution. A little template magic and things become simple(r):
template<typename F>
void EventManager::removeEventHandler(Event::Type evType, F func)
{
auto compare = [func](const Callback& other) -> bool {
F const* f = other.target<F>();
if (f == nullptr) return false;
return *f == func;
};
std::vector<Callback>& callbacks = ...;
auto pend = std::remove_if(callbacks.begin(), callbacks.end(), compare);
callbacks.erase(pend, callbacks.end());
}
template<typename R, typename F, typename L>
void EventManager::removeEventHandler(
Event::Type evType, const boost::_bi::bind_t<R, F, L>& func)
{
auto compare = [&func](const Callback& other) -> bool {
auto const* f = other.target<boost::_bi::bind_t<R, F, L>>();
if (f == nullptr) return false;
return func.compare(*f);
};
std::vector<Callback>& callbacks = ...;
auto pend = std::remove_if(callbacks.begin(), callbacks.end(), compare);
callbacks.erase(pend, callbacks.end());
}
I need to handle Boost.Bind objects separately because operator==
doesn't actually do comparison for Bind objects, but produce a new functor that compares the result of the other two (read more). To compare Boost.Bind you have to use the member function compare()
.
The type boost::_bi::bind_t
seems to be an internal type of Boost (I guess that's what the underscore in namespace '_bi' means), however it should be safe to use it as all overloads of boost::function_equal
also use this type (reference).
This code will work for all types of functors as long as there is an operator==
defined that does comparison, or if you're using Boost.Bind. I had a superficial look into std::bind
(C++0x), but that doesn't seem to be comparable, so it won't work with the code I posted above.
精彩评论