【问题标题】:Using C++11 lambdas asynchronously, safely异步、安全地使用 C++11 lambda
【发布时间】:2012-12-14 04:45:55
【问题描述】:

我从 Objective-C 的背景来到 C++11,我正在努力接受的一件事是 C++11 lambda 与 Objective-C“块”的不同捕获语义。 (请参阅here 进行比较)。

在 Objective-C 中,与 C++ 类似,如果您引用成员变量,则隐式捕获 self/this 指针。但是因为 Objective-C 中的所有对象都是有效的“共享指针”,使用 C++ 术语,你可以这样做:

doSomethingAsynchronously(^{
  someMember_ = 42;
});

... 并且您可以保证在块执行时,您正在访问其成员的对象将是活动的。你不必考虑它。 C++ 中的等价物似乎是这样的:

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

这里,除了this指针之外,你需要记住捕获shared_ptr。有没有一些不易出错的方法来实现这一点?

【问题讨论】:

  • “你不必考虑它。”开始考虑它。它可以带来更好的设计。
  • @Pubby 但问题是,使用块的轻松性使得它们对于 Obj-C 世界中的一次性异步任务如此有用且如此普遍。如果他们有 C++11 语义,并且你不得不问自己“这个对象会活着吗,这个对象会不会活着,这个对象会不会活着......”,我想很多人都会想说“去死吧,我会同步做的。”
  • this 创建共享指针并不能保证它仍然存在,除非对象本身已经由共享指针拥有并且您正在复制它。创建一个新的共享指针(通过new shared_ptr<T>(this)make_shared<T>(this))只会用于双重删除,除非内存会泄漏。那么,就您而言,如果此时不创建共享指针,this 将如何被删除?
  • @NickHutchinson:问题是所有权很重要。例如,查找 Rust -> 由开明的人创建的新语言 -> 所有权是 explicit。向用户隐藏所有权问题会鼓励她不要去想它,很快就会出现空间泄漏和臃肿的内存“我看不出有什么好的理由”。
  • @MSalters 如果没有参数,括号可以省略。 (所以[]{} 是最短的 lambda)

标签: c++ c++11 lambda shared-ptr


【解决方案1】:

C++ 的基本原则之一是不用为不用的东西付费。这意味着在这种情况下,不需要将shared_ptr 转换为this 的上下文不应该产生任何引用计数开销。这也意味着它不应该自动发生,即使例如作为enable_shared_from_this 的一个特性,因为您可能希望将一个短暂的 lambda 传递给算法(for_each 等),在这种情况下 lambda 不会超过其范围。

我建议调整lambda-wrapper pattern;在这种情况下,它用于move 捕获大对象(How to capture std::unique_ptr "by move" for a lambda in 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_from_this之外还继承enable_shared_this_lambda来使用;然后,您可以明确要求任何长期存在的 lambda 都采用共享的 this

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

【讨论】:

  • 是的,但如果涉及线程,您还需要有 someMember_ atomic 或 mutexed。
  • 谢谢,我想我会采用这种模式。仍然不完全相信 shared_ptr 是一个库级别的功能是一件好事,因为它使这变得如此繁琐,但我想它们是休息时间。
【解决方案2】:

Boost 用途:

auto self(shared_from_this());
auto l = [this, self] { do(); };

此处提及:What's the reason of using auto self(shared_from_this()) variable in lambda function?

【讨论】:

    【解决方案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's wrong!
    });
    

    这样做:

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

    这里唯一的代价是您必须在所有内容前面加上strongThis-&gt;。但这是最有意义的方式。

    【讨论】:

      猜你喜欢
      • 2012-07-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-01-19
      • 2012-11-30
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多