Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

memory management for lambda in C++11

Tags:

c++

c++11

Could someone describe why this code doesn't work (on GCC4.7.3 seg-faults before returning from call)?

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

using namespace std;

template<typename F>
auto memo(const F &x) -> std::function<decltype(x())()> {
    typedef decltype(x()) return_type;
    typedef std::function<return_type()> thunk_type;
    std::shared_ptr<thunk_type> thunk_ptr = std::make_shared<thunk_type>();

    *thunk_ptr = [thunk_ptr, &x]() {
        cerr << "First " << thunk_ptr.get() << endl;
        auto val = x();
        *thunk_ptr = [val]() { return val; };
        return (*thunk_ptr)();
    };

    return [thunk_ptr]() { return (*thunk_ptr)(); };
};

int foo() {
    cerr << "Hi" << endl;
    return 42;
}

int main() {
    auto x = memo(foo);
    cout << x() << endl ;
    cout << x() << endl ;
    cout << x() << endl ;
};

My original assumptions:

  1. each std::function<T()> is kinda reference/shared_ptr to some object that represents closure. I.e. life-time of picked up value is limited by it.
  2. std::function<T()> object have assignment operator that will abandon old closure (end life-time picked values) and will take ownership for a new value.

P.S. This question raised after I read question about lazy in C++11

like image 959
ony Avatar asked Oct 01 '13 21:10

ony


People also ask

What is the correct syntax for lambda expression in C ++ 11?

Creating a Lambda Expression in C++auto greet = []() { // lambda function body }; Here, [] is called the lambda introducer which denotes the start of the lambda expression. () is called the parameter list which is similar to the () operator of a normal function.

Do lambdas allocate?

They allocate on the stack, not heap. Their assembly output is identical to using a class. You don't need to capture static variables. If you want to return a lambda, use std::function or similar.

What is a lambda capture?

A capture clause of lambda definition is used to specify which variables are captured and whether they are captured by reference or by value. An empty capture closure [ ], indicates that no variables are used by lambda which means it can only access variables that are local to it.

Where are lambdas stored C++?

Lambda expressions can capture of outer scope variables into a lambda function. The captured variables are stored in the closure object created for the lambda function.


1 Answers

This is the problematic code:

[thunk_ptr, &x]() {
    auto val = x();
    *thunk_ptr = [val]() { return val; };
    return (*thunk_ptr)(); // <--- references a non-existant local variable
}

The problem is that the local thunk_ptr is a copy from the context. That is, in the assignment *thunk_ptr = ... the thunk_ptr refers to the copy owned by the function object. However, with the assignment the function object ceases to exist. That is, on the next line thunk_ptr refers to a just destroyed object.

There are a few approaches to fix the problem:

  1. Instead of getting fancy, just return val. The problem here is that return_type may be a reference type which would cause this approach to fail.
  2. Return the result straight from the assignment: prior to the assignment thunk_ptr is still alive and after the assignment it still return a reference to the std::function<...>() object:

    return (*thunk_ptr = [val](){ return val; })();
    
  3. Safe a copy of thunk_ptr and use this copy to call the function object in the return statement:

    std::shared_ptr<thunk_type> tmp = thunk_ptr;
    *tmp = [val]() { return val; };
    return (*tmp)();
    
  4. Save a copy of reference to std::function and use it instead of referring to field that belongs to overwritten closure:

    auto &thunk = *thunk_ptr;
    thunk = [val]() { return val; };
    return thunk();
    
like image 62
Dietmar Kühl Avatar answered Sep 30 '22 23:09

Dietmar Kühl