16

In the Boost.asio C++11 examples there are snippets like the following:

void do_read()
{
  auto self(shared_from_this());
  socket_.async_read_some(boost::asio::buffer(data_, max_length),
      [this, self](boost::system::error_code ec, std::size_t length)
      {
        if (!ec)
        {
          do_write(length);
        }
      });
}

I understand why the self pointer is needed to keep the class alive (see this question), but I don't understand why the this pointer is also captured. Is it just so that the author can write do_write(length) instead of self->do_write(length) or is there another reason?

Community
  • 1
  • 1
dshepherd
  • 4,989
  • 4
  • 39
  • 46

2 Answers2

6

Without this captured, you cannot call methods of the class from inside the lambda (e. g. do_write). Or access member variables. Granted, you could instead write self->do_write(), but it's both less elegant and potentially slower (because of the shared_ptr involved).

Violet Giraffe
  • 32,368
  • 48
  • 194
  • 335
  • 1
    Just to expand on the speed issue: answers from [this question](http://stackoverflow.com/questions/22295665/how-much-is-the-overhead-of-smart-pointers-compared-to-normal-pointers-in-c) seem to indicate that on old/bad compilers dereferencing a smart pointer may be slower that dereferencing raw pointers, but there should be almost no penalty on modern compilers. – dshepherd Jan 11 '16 at 11:34
  • @dshepherd: fair enough, but it's probably still slower with optimizations disabled (e. g. debug mode). – Violet Giraffe Jan 11 '16 at 11:35
  • @shep I dunno: this is a const pointer to an easy to determine object. The shared ptr is constructed from a weak ptr stored in the objecr, initialized the first time a shared ptr was created from the object. I cannot imagine that there aren't cases where the optimizer has issues with that, let alone the fact that shared from this can be null! – Yakk - Adam Nevraumont Jan 11 '16 at 12:12
  • 1
    I've found the need to capture this in order to call class-methods to work around older (and presumably buggy) versions of GCC. The code compiled just fine without using Clang. – marko Jan 11 '16 at 12:23
  • @Yakk Do you mean `shared_ptr` can be nullptr? Since `shared_from_this` is an inherited member function that cannot possibly be called (without error) from a valid function already contained managed by a `shared_ptr`. –  Jan 12 '16 at 00:39
  • @tech iirc, a standard way to implement shared from this is a class with a weak ptr whose value is initialized at all points where shared ptrs are created: if so, it will be null during construction, and a compiler proving it will always equal `this` is hard. Now that I think about it, are the aliasing ctors a valid way to use shared from this? – Yakk - Adam Nevraumont Jan 12 '16 at 02:07
  • @Yakk I'm thinking within the context of the q/a and how the `shared_ptr` is generated and the object keeps itself alive. There's no `weak_ptr` involved AFAIK, `::shared_from_this` can only be called on an object that is already managed by a `shared_ptr`, and it returns `shared_ptr` not `weak_ptr`. Given this, it cannot possibly return `nullptr` so I was just trying to understand where `nullptr` and `weak_ptr` come in to play. –  Jan 12 '16 at 03:19
  • @VioletGiraffe if you can find an excuse to make an edit, I accidentally have a downvote locked in on this answer. I know, I'm terrible and should have my voting rights revoked. :) –  Jan 12 '16 at 03:21
  • @TechnikEmpire To implement `shared_from_this`, `enable_shared_from_this` can contain a `weak_ptr` to this that is initialized when `make_shared`, or the `shared_ptr` ctor is called on the object. Such an object can exist prior to the shared_ptr existing. Calling `shared_from_this` at that point is UB, but will likely return a null shared_ptr. If that implementation is used, the compiler won't be easily able to optimize `shared_from_this`. Of course, it could only keep a pointer to the reference counting block in `enable_shared_from_this`, and always use the aliasing ctor and `this`... – Yakk - Adam Nevraumont Jan 12 '16 at 03:33
2

The answer given up there is ABSOLUTELY wrong! You should not pass both! Here's how you should do it, based on your code, without passing this:

void do_read()
{
  auto self(shared_from_this());
  socket_.async_read_some(boost::asio::buffer(data_, max_length),
      [self](boost::system::error_code ec, std::size_t length)
      {
        if (!ec)
        {
          self->do_write(length);
        }
      });
}
The Quantum Physicist
  • 24,987
  • 19
  • 103
  • 189
  • 2
    Violet Giraffe's answer seems to makes complete sense to me. Could you elaborate on how it is wrong, and why yours is better? – Quentin May 06 '17 at 13:52
  • @Quentin Quoting "Without this captured, you cannot call methods of the class from inside the lambda". This is purely wrong. `self` can do exactly what `this` does. Also it's wrong to assume that `shared_from_this` is slower (although he said "potentially", which is a useless, defensive, fluidic answer). `operator->` doesn't do any checks, so the least optimization will kill any difference in performance. That answer doesn't give any useful information, but wrong information based on assumptions and generalizations. Sorry for being harsh, but I'm surprised. – The Quantum Physicist May 06 '17 at 18:09
  • After reading the documentation, there shouldn't be any perfomance implications indeed. You do, however, lose direct member access (i.e. no `this->` nor `self->`). – Quentin May 06 '17 at 19:45
  • @Quentin If what u mean with direct member access that we don't have to write `this` explicitly to access members, that's in fact superficial. Compilers make it possible. Effectively, not writing `this` makes the compiler look from inside out and check whether such a member exists, until it finds that `this` has such a member, and uses it. This is why gcc sometimes would tell u that `this` is not captured, because it "guesses" that ur looking for a member in `this`. Well, there's no benefit there. It's just "less text", and just a style thing. To me it feels semantically clearer to use `self`. – The Quantum Physicist May 06 '17 at 19:56
  • 2
    Yes, I know how that works. But the question was "why was `this` captured", and the answer is "only for looks". If you wan't direct member access, you must capture `this`, and that's it. – Quentin May 06 '17 at 20:01
  • @TheQuantumPhysicist Would you still consider using std::bind instead of a 'redirecting' lambda in current days (C++14 and further)? From my experience std::bind is kind of clunky to use especially considering redirecting of arguments. If doing this with std::bind how would you keep the current class alive when it's not part of the closure anymore? – FloWil Sep 08 '21 at 09:22
  • 1
    @FloWil Nope. I don't use bind anymore. – The Quantum Physicist Sep 08 '21 at 09:26
  • 2
    While I think that 'absolutely' wrong is perhaps a bit more strongly worded than is warranted, I do think it's wrong from a resource perspective. That is, we're capturing `this` from a purely stylistic perspective; it does nothing that `self` can't do. However, there's zero overhead these days to `self->`, but there's certainly overhead to redundantly capturing `this`. If one observes the allocation performed by the handler allocator here, it'll likely be the size of a pointer larger with `this` captured. Presumably with an ASIO server one is looking for go-fast, low overhead, so there's that. – Allan Bazinet Feb 05 '22 at 17:12