Consider you want to create a class where you can register a set of listeners to be notified when something occurs. For example, this is a class that performs some task and you want to be notified when the task has been completely executed. See this code in Java:
public class Task { public void doSomething() { } public void addTaskListener(TaskListener t); } public interface TaskListener { void taskFinished(TaskEvent e); } public static void main(String... args) { Task t = new Task(); final String name = "TASK 123"; t.addTaskListener(new TaskListener() { public void taskFinished(TaskEvent e) { System.out.println("Task finished: " + name); } }); t.addTaskListener(new TaskListener() { public void taskFinished(TaskEvent e) { System.out.println("This is a second listener"); } }); t.doSomething(); }
How would you implement it in C++???
I think the first thing that comes to your mind is: “obviously, using function pointers”; but there is no way of passing context information to the function pointers (actually an anonymous class in Java, as in my example, has access to the attributes of the class where it was defined and also to all the local variables marked as final; but from an external function you simply cannot do that.
So how would the C++ way of doing this be?
First, let’s implement the caller. Using C++0x lambdas, it could be as simple as:
int main() { Task t; std::string name = "TASK 123"; t.addTaskListener([&name] { cout << "Task finished: " << name << endl; }); t.addTaskListener([] { cout << "This is a second listener" << endl; }); t.doSomething(); }
Very nice, compact, elegant and powerful: You can pass the name
local variable as a closure to your lambda function.
In the Task implementation, you should have a vector of listeners and access them when the task is executed successfully:
class Task { private: vector<TaskListener> listeners; public: void addTaskListener(TaskListener lis) { listeners.push_back(lis); } void doSomething() { ... invokeListeners(); } private: void invokeListeners() { for (TaskListener lis : listeners) lis(); } };
Did you already realize the problem?
How TaskListener
should be declared?
Could it be a template parameter of a class template? Answer: NO; why? because every lambda function (in my example, 2) is under the hood a class with a functor; so, you could not have ONE to use it for TWO classes.
Ok, you could be tempted to declare your addTaskListener
method like:
template <typename TaskListener> void addTaskListener<Tasklistener t> { listeners.add(t); }
But in that case, you start a new problem: How could you declare your listeners vector to store one element of one class and other one of another?
The cool solution is using a function abstraction taken from the Boost library and available in C++0x: function
.
function
is a template class that wraps a function, an object function (functor) or a method, so it is very useful for our problem:
Thus, in our example, TaskListener
could be declared as:
#include <functional> typedef std::function<void ()> TaskListener;
The parameterized type [void ()
] says that the function does not receive any argument and returns void.
There’s a typo on line 4 of the last example, whereby TypeListener should be TaskListener. Thanks fot the post, I have learnt a lot!
Thanks for your nice comments :) Fixed the typo! :)