18

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 "blocks". (See here for a comparison).

In Objective-C, like C++, the self/this pointer is implicitly captured if you refer to a member variable. But because all objects in Objective-C are effectively "shared pointers", to use the C++ terminology, you can do this:

doSomethingAsynchronously(^{
  someMember_ = 42;
});

... and you're guaranteed that the object whose member you're accessing will be alive when the block executes. You don't have to think about it. The equivalent in C++ seems to be something like:

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

Here, you need to remember to capture the shared_ptr in addition to the this pointer. Is there some less error-prone way of achieving this?

Nick Hutchinson
  • 5,044
  • 5
  • 33
  • 34
  • 3
    *"You don't have to think about it."* Start thinking about it. It leads to better design. – Pubby Dec 14 '12 at 04:48
  • @Pubby But the thing is, it's the effortlessness of using blocks that makes them so useful and so pervasive for one-shot asynchronous tasks in the Obj-C world. If they had the C++11 semantics, and you had to ask yourself "will this object be alive, will this object be alive, will this object be alive..." every single time, I think many people would be tempted to say "screw it, I'll do it synchronously." – Nick Hutchinson Dec 14 '12 at 05:15
  • Creating a shared pointer from `this` doesn't guarantee it will still exist, unless the object itself was already owned by a shared pointer and you are copying it. Creating a new shared pointer (by `new shared_ptr(this)` or `make_shared(this)`) will only serve to get a double-delete, unless the memory would have leaked otherwise. So, in your case, how does `this` get deleted if you don't create a shared pointer at this point? – Agentlien Dec 14 '12 at 06:16
  • @Agentlien I've clarified the question. – Nick Hutchinson Dec 14 '12 at 06:42
  • 7
    @NickHutchinson: The problem is **ownership is important**. Look up Rust, for example -> new language created by enlightened people -> ownership is *explicit*. Hiding ownership issues from the user encourages her not to think about it, and soon you get space leaks and bloated memory "for no good reason I can see". – Matthieu M. Dec 14 '12 at 09:10
  • IIRC the C++11 lambda syntax is `[](){}`, not `([]{})`. Also, a lambda is an expression, so you can't form a declaration with just `doSomethingAsynchronously [](){}`. That's equivalent to just `Foo 4`. – MSalters Dec 14 '12 at 11:49
  • 4
    @MSalters The parentheses can be omitted if there are no arguments. (So `[]{}` is the shortest lambda) – R. Martinho Fernandes Dec 14 '12 at 13:04

3 Answers3

8

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;
}));
Community
  • 1
  • 1
ecatmur
  • 152,476
  • 27
  • 293
  • 366
  • Yes, but you also need to have someMember_ atomic or mutexed if threads are involved. – Johan Lundberg Dec 14 '12 at 20:36
  • Thanks, I think I'll adopt this pattern. Still not entirely convinced it's a good thing that shared_ptr is a library-level feature given that it makes this so cumbersome, but them's the breaks, I guess. – Nick Hutchinson Jan 03 '13 at 11:39
6

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?

Community
  • 1
  • 1
JE42
  • 4,881
  • 6
  • 41
  • 51
3

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.

The Quantum Physicist
  • 24,987
  • 19
  • 103
  • 189