Объект обратного вызова разрушается при выполнении при замене себя новым значением

Рассмотрим следующую ситуацию:

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.
});
}

Предполагаемая семантика в этой ситуации состоит в том, чтобы все обратные вызовы вызывались, когда они установлены, и последний установленный набор сохранялся в callback когда вершина set_callback возвращается.

Решения, которые я вижу:

  1. замещать unique_function<void()> с чем-то копируемым, как std::shared_ptr<unique_function<void()>> или специализированный shared_function<void()> а затем вызвать копию:

    shared_function<void()> callback = []{};
    
    void set_callback(shared_function<void()> new_callback) {
    callback = std::move(new_callback);
    auto callback_copy = callback;
    callback_copy();
    }
    

    Недостатки:

    1. Накладные расходы или подсчет ссылок.
  2. Иметь список и добавлять к нему обратные вызовы перед их выполнением. Когда вершина set_callback возвращается, callbacks.back() является последним установленным обратным вызовом, а остальные могут быть удалены. Для этого требуется дополнительный счетчик, отслеживающий текущую глубину set_callback звонки.

    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()));
    }
    

    Недостатки:

    1. Накладные расходы на список обратных вызовов и счетчик глубины.

Можем ли мы сделать лучше?

0

Решение

Задача ещё не решена.

Другие решения

Других решений пока нет …