16

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.

Michael Goldshteyn
  • 71,784
  • 24
  • 131
  • 181
  • 6
    You could do what MS did with connection points: Return a cookie (index into your vector) that the caller provides to deregistration. – WhozCraig Apr 10 '13 at 03:04
  • 3
    I often store interface pointers as callbacks and put them in a set. Then I can access by value to unregister. – Roger Rowland Apr 10 '13 at 03:06
  • @WhozCraig Boost does that for one of their classes also. – Xymostech Apr 10 '13 at 03:17
  • [Storing boost::function objects in a container, UnregisterCallback](http://stackoverflow.com/a/13095974/1762344), [Why is std::function not equality comparable?](http://stackoverflow.com/a/13105074/1762344) – Evgeny Panasyuk Apr 10 '13 at 04:02
  • possible duplicate of [Storing boost::function objects in a container](http://stackoverflow.com/questions/13094720/storing-boostfunction-objects-in-a-container) – Nicol Bolas Apr 10 '13 at 06:28
  • You may consider also Boost.Signals2. – Andy Prowl Apr 10 '13 at 08:13
  • In your example it sounds like `boost::signals` is what you require. In the past I have used a `std::map` you could always have the `register` function accept a key along with the callback, whcih can also be supplied when calling `unregister`. – mark Apr 10 '13 at 08:20
  • @WhozCraig: I don't recommend using the index in the vector as a cookie. If you unregister the first callback, all cookies will be invalidated! – André Caron Apr 10 '13 at 15:36
  • @EvgenyPanasyuk's answer on the linked question is what you want; it works equally with `std::function` as with `boost::function`. – ecatmur Apr 10 '13 at 15:40
  • @AndréCaron Certainly. The index was merely an example of what a cookie *could* be. I'd likely use a std::list<> and an iterator for the cookie, or something similar. – WhozCraig Apr 10 '13 at 16:16

2 Answers2

14

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.

Adam H. Peterson
  • 4,511
  • 20
  • 28
0

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_ptrs 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.