Использование 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 в дополнение к этому указателю. Есть ли какой-то менее склонный к ошибкам способ достичь этого?
Ответы
Ответ 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;
}));
Ответ 2
Boost использует:
auto self(shared_from_this());
auto l = [this, self] { do(); };
Упоминается здесь: В чем причина использования переменной auto self (shared_from_this()) в лямбда-функции?
Ответ 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->
. Но это самый осмысленный способ сделать это.