Reputation: 74390
The question is strictly about std::function
and not boost::function
. See the Update section at the bottom of this question for more details, especially the part about it not being possible to compare non-empty std::function
objects per the C++11 standard.
The C++11 std::function
class template is great for maintaining a collection of callbacks. One can store them in a vector
, for example and invoke them when need be. However, maintaining these objects and allowing for unregistration seems to be impossible.
Let me be specific, imagine this class:
class Invoker
{
public:
void Register(std::function<void()> f);
void Unregister(std::function<void()> f);
void InvokeAll();
private:
// Some container that holds the function objects passed to Register()
};
Sample usage scenario:
void foo()
{
}
int main()
{
std::function<void()> f1{foo};
std::function<void()> f2{[] {std::cout << "Hello\n";} };
Invoker inv;
// The easy part
// Register callbacks
inv.Register(f1);
inv.Register(f2);
// Invoke them
inv.InvokeAll();
// The seemingly impossible part. How can Unregister() be implemented to actually
// locate the correct object to unregister (i.e., remove from its container)?
inv.Unregister(f2);
inv.Unregister(f1);
}
It is fairly clear how the Register()
function can be implemented. However, how would one go about implementing Unregister()
. Let's say that the container that holds the function objects is vector<std::function<void()>>
. How would you find a particular function object that is passed to the Unregister()
call? std::function
does supply an overloaded operator==
, but that only tests for an empty function object (i.e., it cannot be used to compare two non-empty function objects to see if they both refer to the same actual invocation).
I would appreciate any ideas.
Update:
Ideas so far mainly consist of the addition of a cookie to be associated with each std::function
object that can be used to unregister it. I was hoping for something that is not exogenous to the std::function
object itself. Also, there seems to be much confusion between std::function
and boost::function
. The question is strictly about std::function
objects, and not boost::function
objects.
Also, you cannot compare two non-empty std::function
objects for equality. They will always compare non-equal per the standard. So, links in the comments to solutions that do just that (and use boost::function
objects to boot) are patently wrong in the context of this question.
Upvotes: 16
Views: 3949
Reputation: 1
I have an idea for this.
Store the callbacks as std::weak_ptr<std::function<void(argtype1, argtype1)>>
. Then the caller is responsible for keeping the corresponding std::shared_ptr
alive, and all the caller has to do to unregister the callback is destroy all active std::shared_ptr
s to the callback function.
When invoking callbacks, the code has to be careful to check for lock failures on the std::weak_ptr<>
s it is using. When it runs across these it can remove them from its container of registered callbacks.
Note that this does not give complete thread safety, as the callback invoker can lock the std::weak_ptr
and make a temporarily newly active std::shared_ptr
of the callback function that can stay alive after the caller's std::shared_ptr
goes out of scope.
Upvotes: 0
Reputation: 4591
Since you can't test for element identity in the container, it's probably best to use a container (such as std::list
) whose iterators do not invalidate when the container is modified, and return iterators back to registering callers that can be used to unregister.
If you really want to use vector
(or deque
), you could return the integral index into the vector/deque when the callback is added. This strategy would naturally require you to make sure indexes are usable in this fashion to identify the function's position in the sequence. If callbacks and/or unregistration is rare, this could simply mean not reusing spots. Or, you could implement a free list to reuse empty slots. Or, only reclaim empty slots from the ends of the sequence and maintain a base index offset that is increased when slots are reclaimed off the beginning.
If your callback access pattern doesn't require random access traversal, storing the callbacks in a std::list
and using raw iterators to unregister seems simplest to me.
Upvotes: 14