Using C++11 lambdas asynchronously, safely

后端 未结 3 1371
失恋的感觉
失恋的感觉 2021-01-31 11:06

I\'ve come to C++11 from an Objective-C background, and one thing I\'m struggling to come to terms with is the different capturing semantics of C++11 lambdas vs Objective-C \"bl

相关标签:
3条回答
  • 2021-01-31 11:31

    Boost uses:

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

    Mentioned here: What's the reason of using auto self(shared_from_this()) variable in lambda function?

    0 讨论(0)
  • 2021-01-31 11:43

    One of the founding principles of C++ is that you don't pay for what you don't use. That means in this case that contexts where taking a shared_ptr to this is unnecessary shouldn't incur any reference counting overhead. This also means that it shouldn't happen automatically even e.g. as a feature of enable_shared_from_this, since you might want to pass a short-lived lambda to an algorithm (for_each, etc.) in which case the lambda doesn't outlive its scope.

    I'd suggest adapting the lambda-wrapper pattern; in that case it's used for move capture of a large object (How to capture std::unique_ptr "by move" for a lambda in std::for_each), but it can equally be used for shared capture of 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);
      }
    };
    

    Use by inheriting enable_shared_this_lambda in addition to enable_shared_from_this; you can then explicitly request that any long-lived lambdas take a shared this:

    doSomethingAsynchronously(make_shared_this_lambda([this] {
      someMember_ = 42;
    }));
    
    0 讨论(0)
  • 2021-01-31 11:53

    Actually, there's one right answer to this problem. The answer has the exact same effect of binding with shared_from_this() (like when you do it with boost::asio::io_service). Think about it; what does binding with shared_from_this() do? It simple replaces this. So what prevents you from replacing this with shared_from_this() totally?

    Following your example, which I updated to make the difference clearer, instead of this:

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

    Do this:

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

    The only cost here is that you're gonna have to prefix everything with strongThis->. But this is the most meaningful way to do it.

    0 讨论(0)
提交回复
热议问题