Использование lambdas С++ 11 асинхронно, безопасно - программирование
Подтвердить что ты не робот

Использование lambdas С++ 11 асинхронно, безопасно

Я пришел на С++ 11 из фона Objective-C, и одна вещь, с которой я пытаюсь договориться, - это различные захватывающие семантики блоков С++ 11 lambdas vs Objective-C. (См. здесь для сравнения).

В Objective-C, как и С++, указатель self/this неявно захватывается, если вы ссылаетесь на переменную-член. Но поскольку все объекты в Objective-C являются эффективными "общими указателями", чтобы использовать терминологию С++, вы можете сделать это:

doSomethingAsynchronously(^{
  someMember_ = 42;
});

... и вам гарантируется, что объект, чей член, к которому вы обращаетесь, будет активным, когда выполняется блок. Вам не нужно об этом думать. Эквивалент в С++ выглядит примерно так:

// I'm assuming here that `this` derives from std::enable_shared_from_this and 
// is already owned by some shared_ptr.
auto strongThis = shared_from_this();

doSomethingAsynchronously([strongThis, this] {
  someMember_ = 42;   // safe, as the lambda holds a reference to this
                      // via shared_ptr.
});

Здесь вам нужно запомнить захват shared_ptr в дополнение к этому указателю. Есть ли какой-то менее склонный к ошибкам способ достичь этого?

4b9b3361

Ответ 1

Одним из основополагающих принципов С++ является то, что вы не платите за то, что не используете. Это означает, что в этом случае контексты, в которых требуется отнести shared_ptr to this, не должны нести накладные расходы на подсчет ссылок. Это также означает, что это не должно происходить автоматически, например, как особенность enable_shared_from_this, так как вам может понадобиться передать короткоживущую лямбду на алгоритм (for_each и т.д.), и в этом случае лямбда не переживет его область действия.

Я предлагаю адаптировать шаблон lambda-wrapper; в этом случае он использовал для move захват большого объекта (Как записать std:: unique_ptr "путем перемещения" для лямбда в std:: for_each), но он может в равной степени использоваться для совместного захвата this:

template<typename T, typename F>
class shared_this_lambda {
  std::shared_ptr<T> t;  // just for lifetime
  F f;
public:
  shared_this_lambda(std::shared_ptr<T> t, F f): t(t), f(f) {}
  template<class... Args>
  auto operator()(Args &&...args)
  -> decltype(this->f(std::forward<Args>(args)...)) {
    return f(std::forward<Args>(args)...);
  }
};

template<typename T>
struct enable_shared_this_lambda {
  static_assert(std::is_base_of<std::enable_shared_from_this<T>, T>::value,
    "T must inherit enable_shared_from_this<T>");
  template<typename F>
  auto make_shared_this_lambda(F f) -> shared_this_lambda<T, F> {
    return shared_this_lambda<T, F>(
      static_cast<T *>(this)->shared_from_this(), f);
  }
  template<typename F>
  auto make_shared_this_lambda(F f) const -> shared_this_lambda<const T, F> {
    return shared_this_lambda<const T, F>(
      static_cast<const T *>(this)->shared_from_this(), f);
  }
};

Используйте, наследуя enable_shared_this_lambda в дополнение к enable_shared_from_this; вы можете затем явно запросить, чтобы любые долгоживущие лямбды принимали общий this:

doSomethingAsynchronously(make_shared_this_lambda([this] {
  someMember_ = 42;
}));

Ответ 3

Собственно, есть один правильный ответ на эту проблему. Ответ имеет тот же эффект связывания с shared_from_this() (например, когда вы делаете это с помощью boost::asio::io_service). Думаю об этом; что делает привязка с shared_from_this()? Он просто заменяет this. Итак, что мешает вам полностью заменить this на shared_from_this()?

Следуя вашему примеру, который я обновил, чтобы сделать разницу более четкой, вместо этого:

auto strongThis = shared_from_this();

doSomethingAsynchronously([strongThis, this] () {
  this->someMember_ = 42; //here, you're using `this`... that wrong!
});

Сделайте это:

auto strongThis = shared_from_this();

doSomethingAsynchronously([strongThis] () //notice, you're not passing `this`!
{
  strongThis->someMember_ = 42;            
});

Единственная стоимость здесь заключается в том, что вам нужно будет префикс всего strongThis->. Но это самый осмысленный способ сделать это.