Consider the following situation:
struct A {
// `unique_function` is like std::function but does not
// require its target to be Copyable, and is in turn
// itself not Copyable either.
unique_function<void()> callback = []{};
void set_callback(unique_function<void()> new_callback) {
callback = std::move(new_callback);
callback();
}
};
int main() {
A a;
a.set_callback([&, x = std::make_unique<int>(42)]{
std::cout << "Ok at this point, *x = " << *x << ".\n";
a.set_callback([]{}); // This destroys enclosing lambda
// while is is being executed, thus messing things up.
// Captures are not legal to access here.
});
}
The intended semantics in this situation is to have all callbacks called when they are set, and have the last one set be stored within callback
when the top set_callback
returns.
Solutions I can see:
Replace
unique_function<void()>
with something Copyable, likestd::shared_ptr<unique_function<void()>>
or specializedshared_function<void()>
and then call the copy:shared_function<void()> callback = []{}; void set_callback(shared_function<void()> new_callback) { callback = std::move(new_callback); auto callback_copy = callback; callback_copy(); }
Drawbacks:
- Overhead or reference counting.
Have a list, and append callbacks to it before executing them. When top
set_callback
returns,callbacks.back()
is the last callback set, and the rest can be erased. This requires an additional counter tracking current depth ofset_callback
calls.std::deque<unique_function<void()>> callbacks; std::size_t depth = 0; struct depth_controller { std::size_t& depth; depth_controller(std::size_t& d) : depth(d) { ++depth; } ~depth_controller() { --depth; } }; void set_callback(shared_function<void()> new_callback) { callbacks.emplace_back(std::move(new_callback)); { depth_controller dctl{depth}; callbacks.back()(); } if (depth > 0) { return; } callbacks.erase(callbacks.begin(), std::prev(callbacks.end())); }
Drawbacks:
- Overhead of callback list and depth counter.
Can we do better?