Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ Lambdas, Capturing, Smart Ptrs, and the Stack: Why Does this Work?

I've been playing around with some of the new features in C++11, and I tried to write the following program, expecting it not to work. Much to my surprise, it does (on GCC 4.6.1 on Linux x86 with the 'std=c++0x' flag):

#include <functional>
#include <iostream>
#include <memory>

std::function<int()> count_up_in_2s(const int from) {
    std::shared_ptr<int> from_ref(new int(from));
    return [from_ref]() { return *from_ref += 2; };
}

int main() {
    auto iter_1 = count_up_in_2s(5);
    auto iter_2 = count_up_in_2s(10);

    for (size_t i = 1; i <= 10; i++)
        std::cout << iter_1() << '\t' << iter_2() << '\n'
        ;
}

I was expecting 'from_ref' to be deleted when each execution of the returned lambda runs. Here's my reasoning: once count_up_in_2s is run, from_ref is popped off the stack, yet because the returned lambda isn't neccessarily ran straight away, since it's returned, there isn't another reference in existence for a brief period until the same reference is pushed back on when the lambda is actually run, so shouldn't shared_ptr's reference count hit zero and then delete the data?

Unless C++11's lambda capturing is a great deal more clever than I'm giving it credit for, which if it is, I'll be pleased. If this is the case, can I assume that C++11's variable capturing will allow all the lexical scoping/closure trickery a la Lisp as long as /something/ is taking care of dynamically allocated memory? Can I assume that all captured references will stay alive until the lambda itself is deleted, allowing me to use smart_ptrs in the above fashion?

If this is as I think it is, doesn't this mean that C++11 allows expressive higher-order programming? If so, I think the C++11 committee did an excellent job =)

like image 434
Louis Avatar asked Dec 19 '11 04:12

Louis


2 Answers

The lambda captures from_ref by value, so it makes a copy. Because of this copy, the ref count is not 0 when from_ref gets destroyed, it is 1 because of the copy that still exists in the lambda.

like image 111
Seth Carnegie Avatar answered Oct 15 '22 03:10

Seth Carnegie


The following:

std::shared_ptr<int> from_ref(new int(from));
return [from_ref]() { return *from_ref += 2; };

is mostly equivalent to this:

std::shared_ptr<int> from_ref(new int(from));
class __uniqueLambdaType1432 {
  std::shared_ptr<int> capture1;
 public:        
  __uniqueLambdaType1432(std::shared_ptr<int> capture1) :
    capture1(capture1) { 
  }
  decltype(*capture1 += 2) operator ()() const {
    return *capture1 += 2;
  }
};
return __uniqueLambdaType1432(from_ref);

where __uniqueLambdaType1432 is a program-globally unique type distinct from any other, even other lambda types generated by a lexically identical lambda expression. Its actual name is not available to the programmer, and besides the object resulting from the original lambda expression, no other instances of it can be created because the constructor is actually hidden with compiler magic.

like image 27
JohannesD Avatar answered Oct 15 '22 01:10

JohannesD