Jump to content

  • Log In with Google      Sign In   
  • Create Account





The New C++ - functions

Posted by Washu, 15 March 2012 · 3,746 views

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 <functional> header.

Function and Bind in <functional>
The <functional> 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 <iostream>
#include <functional>
#include <vector>

struct EventArgs {};

class MulticastEvent {
public:
	typedef std::function<void(EventArgs const&)> 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<EventSignature> functions;
};

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

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

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

	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 "<<i<<"."<<std::endl;
}

struct EventHandler {
	void MemberFunction(EventArgs const&) {
		std::cout<<"EventHandler::MemberFunction."<<std::endl;
	}
}
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<void(EventArgs const&)> 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<int, EventSignature> 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 <functional> Bits
In addition to the major pieces above, the <functional> 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 <iostream>
#include <functional>
#include <map>

template<class EventArgType>
class MulticastEvent {
public:
	typedef std::function<void(EventArgType const&)> 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<int, EventSignature> 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: ("<<args.x<<", "<<args.y<<") for button: "<<args.button<<std::endl;
}

int main() {
	MulticastEvent<MouseClickEventArgs> myEvent;
	myEvent += HandleMouseClick;
	myEvent(MouseClickEventArgs(16, 37, 1));
}





This is awesome. Closures and delegates are probably my favorite features in .NET. Now it seems C++ is getting a similar upgrade!
Besides this thing does not work as AddDelegate is supposed to be AddHandler. But ok, we get over this.
This relies on a subtle detail: the fact that push_back performs a value copy.
Please see this thread on passing around lambdas. 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.
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.
Not exactly, it has much deeper implications in my opinion.

Recent Comments

October 2014 »

S M T W T F S
   1234
567891011
12131415161718
192021222324 25
262728293031 
PARTNERS