This class is mostly an educational exercise for me using some C++11 constructs. I wanted to create something similar to an "invocation list" in C#, i.e., a list of zero or more function objects which should be called when the invocation list as a whole is invoked. I'm certain there's room for improvement here and I'm hoping I didn't mess anything up TOO badly.
Some key implementation notes:
- Variadic templates are used to specify the parameters passed to the target(s) of the invocation list.
- The
subscribe()
function returns an "opaque" subscription object which can be used tounsubscribe()
from the invocation list. This is done becausestd::function
is not equality-comparable (so passing in astd::function
to unsubscribe is problematic). - The subscription object that gets returned can be copied, and is designed so that instances of it are still well-behaved in a "detached" state when the original
invocation_list
instance has been destroyed; this is accomplished by sharing a pointer to the underlying target list usingstd::shared_ptr
to do the reference counting on that pointer. The pointer is nulled when the invocation list is destroyed, detaching any associated lingering subscription instances. - No attempt has been or will be made to make this thread-safe.
#pragma once
#include <functional>
#include <list>
#include <memory>
template <class... Args>
class invocation_list
{
private:
typedef std::function<void(Args...)> func_type;
typedef std::list<func_type> list_type;
public:
class subscription
{
public:
void unsubscribe()
{
if (_targets && *_targets)
{
(*_targets)->erase(_it);
_targets.reset();
}
}
private:
subscription(
std::shared_ptr<list_type *> targets,
typename list_type::iterator it)
: _targets(targets), _it(it) { }
std::shared_ptr<list_type *> _targets;
typename list_type::iterator _it;
friend class invocation_list;
};
public:
invocation_list() : _token(new list_type *)
{
*_token = &_targets;
}
~invocation_list()
{
*_token = nullptr;
}
invocation_list(const invocation_list&) = delete;
invocation_list& operator=(const invocation_list&) = delete;
subscription subscribe(func_type func)
{
_targets.push_back(func);
return subscription(_token, --_targets.end());
}
void operator()(Args... args) const
{
for (auto it = _targets.begin(); it != _targets.end(); ++it)
(*it)(args...);
}
private:
list_type _targets;
std::shared_ptr<list_type *> _token;
};
This would be used like:
invocation_list<int> foo;
auto a = foo.subscribe([](int x) { std::cout << "hello: " << x << std::endl; });
auto b = foo.subscribe([](int x) { std::cout << "world: " << x << std::endl; });
foo(42); // prints "hello: 42" "world: 42"
a.unsubscribe();
foo(54); // prints only "world: 54"
Future changes I've considered and am interested in feedback on:
- Overload operators for subscribing and unsubscribing instead of an explicit
subscribe()
function? - Extending the subscription type to optionally operate in an RAII mode where the target is automatically unsubscribed when the subscription object goes out of scope?
operator()
with a range-based for loop:for (auto target : _targets) target(args...);
\$\endgroup\$