The New C++ - functions

Published March 15, 2012
Advertisement
C++11 significantly expanded the C++ standard library with a number of new libraries and functionality, which isn't actually all that new if you've used boost. These libraries add a great deal of needed functionality, although it still doesn't compare to the standard library you get with many other languages.

Two of the new extensions to the standard library are function wrappers and arbitrary function binding, located in the header.

Function and Bind in


The header has been expanded with a whole slew of new capabilities. Amongst those are two main things that stand out as being of particular use are the function binding capabilities and the generic function container.

std::function


The std::function class allows you to hold an arbitrary callable object. That includes function pointers, functors, and lambdas. The class takes a signature of the function that it expects to contain as a template parameter, and overloads the parenthesis operator (thus is its self a functor) to present a callable interface.

At first glance you might be wondering what the use is, but if you think about it for a short spell, many possible uses of a type safe arbitrary function container will pop out. Amongst those many possible uses is one in particular, which we shall be using as a demonstration purpose of many concepts. Our example will be that of an event dispatcher, which allows for arbitrary event handlers to be registered. The implementation will be somewhat generic for demonstration purposes, but a very explicit example that you could consider using would be one for Win32 events.

We'll first start off with a basic event arguments object, which will hold the arguments we wish to pass to our event, an example would be the location of the mouse during a mouse click event. Then we'll define a basic multicast event, which will hold a vector of handlers who have registered to receive notification of that event. We'll also provide a method to remove all registered handlers. An implementation that removes specific handlers will be covered later; however we have to employ some tricks to get around issues that will be covered in a bit.
#include
#include
#include

struct EventArgs {};

class MulticastEvent {
public:
typedef std::function EventSignature;

public:
void AddHandler(EventSignature const& f) {
functions.push_back(f);
}

void Clear() {
functions.clear();
}

void Invoke(EventArgs const& arg) {
//for each(auto const& f in functions) in Visual Studio
for(auto const& f : functions) {
f(arg);
}
}

private:
std::vector functions;
};

void handler(EventArgs const&) {
std::cout<<"Handler function."<}

struct EventHandler {
void operator()(EventArgs const&) {
std::cout<<"EventHandler::operator()."< }
};

int main() {
MulticastEvent myEvent;
myEvent.AddDelegate(handler);
myEvent.AddDelegate(EventHandler());
myEvent.AddDelegate([](EventArgs const& arg) { std::cout<<"Lambda function."<
myEvent.Invoke(EventArgs());
}

We can see the usefulness of this simple block of code, as we can now implement any arbitrary event dispatcher. It is useful to note that std::function treats functors (that overload operator()) the same as functions. We'll see how to bind arbitrary member functions in a bit. We can also see that lambdas can be encapsulated in std::function objects, thus enabling you to provide a very generic, yet typesafe, callback mechanism.

std::bind and placeholders


In addition to the function wrapper class above new binding capabilities were added to the standard library as well. You might be familiar with bind1st and bind2nd, two functions in C++98 which returned functors that bound parameters to the 1st and 2nd arguments respectively. In C++11 we get full generic parameter binding through the std::bind function.

The std::bind function takes a set of arguments whereby the first argument is the function to apply the binding to, and the remaining arguments are either values to bind to the argument at that position, or placeholders for the resulting functor's arguments. The placeholders are found in the std::placeholders namespace. An example will show this off best:
void BoundHandler(int i, EventArgs const&) {
std::cout<<"Bound Handler function "<}

struct EventHandler {
void MemberFunction(EventArgs const&) {
std::cout<<"EventHandler::MemberFunction."< }
}
int main() {
MulticastEvent myEvent;
myEvent.AddHandler(std::bind(BoundHandler, 1, std::placeholders::_1));

EventHandler handler;
myEvent.AddHandler(std::bind(&EventHandler::MemberFunction, handler, std::placeholders::_1));
myEvent.Invoke(EventArgs());
}

As can be seen, the first handler has its first parameter bound to the literal integer 1, while its second parameter is bound to the placeholder _1, which then gets replaced with the event argument passed to Invoke(). The second handler passes in a non-static member function with its "this" parameter bound to the instance we wish it to be invoked against. Now, it is important to note that when 'handler' goes out of scope we end up in a nasty situation where our MulticastEvent contains an std::function whose functor is pointing to an object which is now no longer in scope. Thus we now need to work on adding removal to our MulticastEvent.

Implementing Event Deregistration


The main problem with attempting to compare two std::function objects is, simply put, you cannot be sure that the two contained "function objects" are even comparable. An example is a stateless functor that you simply construct when you create the std::function object, as shown in the first example.

Thus we need to provide an alternative method for removing function objects from our MulticastEvent. Perhaps the simplest mechanism to use is to simply return a handle which can later be used to find and remove the specific event. Doing this will require a few minor changes to our setup: We'll want to provide a mapping mechanism from handle to std::function, so we'll wish to change our container from a std::vector to a std::map (or similar container such as std::unordered_map). We'll also need to provide a handle type for returning, which in our case will be a simple integral ID. Lastly we'll need a function that, when given a handle, removes the appropriate event handler.
class MulticastEvent {
public:
typedef std::function EventSignature;
typedef int EventHandle;

public:
MulticastEvent() : eventHandle(0) {}

EventHandle AddHandler(EventSignature const& f) {
functions.insert(std::make_pair(++eventHandle, f));
return eventHandle;
}

void RemoveHandler(EventHandle handle) {
functions.erase(handle);
}

void Clear() {
functions.clear();
}

void Invoke(EventArgs const& arg) {
//for each(auto const& f in functions) in Visual Studio
for(auto const& f : functions) {
f.second(arg);
}
}

private:
EventHandle eventHandle;
std::map functions;
};

This proves to be pretty simple, and wrapping up the integer in a typedef allows us to replace the handle with a structure that provides some form of scoping (and perhaps implicit release on destruction).

Additional Bits


In addition to the major pieces above, the header has a few other new additions. Of particular interest to us is the hash class, which is used by the unordered associative containers hashing the key elements. std::reference_wrapper which is a class that wraps up a reference, allowing for copying and assignment (unlike a reference). std::reference_wrapper is constructed using two new utility functions: std::ref and std::cref, which return a wrapper to a reference or a constant reference, respectively. There are also two utility type information classes provided: std::is_bind_expression whose static value member is true when the type is a bind expression, and std::is_placeholder that again has a static value member that is true when the type is a placeholder.

Conclusion


I mentioned earlier the ability to handle Win32 events, and the above MulticastEvent is certainly capable of this. However with one minor change we can make it a lot more capable than it currently is. Specifically, if we template the MulticastEvent to allow us to specify the type of the argument we wish to pass we can eliminate the EventArgs base class entirely, we can also add some operator overloading to clean up the addition and removal of handlers along with the invocation of the MulticastEvent object. Thus we end up with the following basic piece code for handling arbitrary events, including (as demonstrated) mouse click events:
#include
#include
#include

template
class MulticastEvent {
public:
typedef std::function EventSignature;
typedef int EventHandle;

public:
MulticastEvent() : eventHandle(0) {}

EventHandle AddHandler(EventSignature const& f) {
functions.insert(std::make_pair(++eventHandle, f));
return eventHandle;
}

void RemoveHandler(EventHandle handle) {
functions.erase(handle);
}

void Clear() {
functions.clear();
}

MulticastEvent& operator+=(EventSignature const& f) {
AddHandler(f);
return *this;
}

MulticastEvent& operator-=(EventHandle handle) {
RemoveHandler(handle);
return *this;
}

void operator()(EventArgType const& arg) {
//for each(auto const& f in functions) in Visual Studio
for(auto const& f : functions) {
f.second(arg);
}
}

private:
EventHandle eventHandle;
std::map functions;
};

struct MouseClickEventArgs {
MouseClickEventArgs(int x, int y, int button) : x(x), y(y), button(button) {}
int x, y, button;
};

void HandleMouseClick(MouseClickEventArgs const& args) {
std::cout<<"Mouse click at: ("<}

int main() {
MulticastEvent myEvent;
myEvent += HandleMouseClick;
myEvent(MouseClickEventArgs(16, 37, 1));
}
Previous Entry The New C++ - lambdas
3 likes 4 comments

Comments

evanofsky
This is awesome. Closures and delegates are probably my favorite features in .NET. Now it seems C++ is getting a similar upgrade!
April 02, 2012 05:10 PM
Krohm
Besides this thing does not work as [font=courier new,courier,monospace]AddDelegate [/font]is supposed to be [font=courier new,courier,monospace]AddHandler[/font]. But ok, we get over this.
This relies on a subtle detail: the fact that [font=courier new,courier,monospace]push_back [/font]performs a value copy.
Please see this thread on [url="http://www.gamedev.net/topic/624281-assigning-lambdas/"]passing around lambdas[/url]. The whole point: don't pass function objects by reference. And be extremely careful on how to define the function call. I'm still not sure I get that correctly.
May 02, 2012 07:30 AM
Washu
It can be a copy or a move constructor. Either will produce the same behavior. The AddDelegate thing is a good point, in the first code sample I did say AddDelegate when I meant AddHandler.

Your issue in that thread is that you were taking a reference to an unnamed temporary. As pointed out in my reply.
May 02, 2012 08:06 AM
Krohm
Not exactly, it has much deeper implications in my opinion.
May 03, 2012 05:12 AM
You must log in to join the conversation.
Don't have a GameDev.net account? Sign up!
Advertisement