117

I would like some information on how to correctly think about C++11 closures and std::function in terms of how they are implemented and how memory is handled.

Although I don't believe in premature optimisation, I do have a habit of carefully considering the performance impact of my choices while writing new code. I also do a fair amount of real-time programming, e.g. on microcontrollers and for audio systems, where non-deterministic memory allocation/deallocation pauses are to be avoided.

Therefore I'd like to develop a better understanding of when to use or not use C++ lambdas.

My current understanding is that a lambda with no captured closure is exactly like a C callback. However, when the environment is captured either by value or by reference, an anonymous object is created on the stack. When a value-closure must be returned from a function, one wraps it in std::function. What happens to the closure memory in this case? Is it copied from the stack to the heap? Is it freed whenever the std::function is freed, i.e., is it reference-counted like a std::shared_ptr?

I imagine that in a real-time system I could set up a chain of lambda functions, passing B as a continuation argument to A, so that a processing pipeline A->B is created. In this case, the A and B closures would be allocated once. Although I'm not sure whether these would be allocated on the stack or the heap. However in general this seems safe to use in a real-time system. On the other hand if B constructs some lambda function C, which it returns, then the memory for C would be allocated and deallocated repeatedly, which would not be acceptable for real-time usage.

In pseudo-code, a DSP loop, which I think is going to be real-time safe. I want to perform processing block A and then B, where A calls its argument. Both these functions return std::function objects, so f will be a std::function object, where its environment is stored on the heap:

auto f = A(B);  // A returns a function which calls B
                // Memory for the function returned by A is on the heap?
                // Note that A and B may maintain a state
                // via mutable value-closure!
for (t=0; t<1000; t++) {
    y = f(t)
}

And one which I think might be bad to use in real-time code:

for (t=0; t<1000; t++) {
    y = A(B)(t);
}

And one where I think stack memory is likely used for the closure:

freq = 220;
A = 2;
for (t=0; t<1000; t++) {
    y = [=](int t){ return sin(t*freq)*A; }
}

In the latter case the closure is constructed at each iteration of the loop, but unlike the previous example it is cheap because it is just like a function call, no heap allocations are made. Moreover, I wonder if a compiler could "lift" the closure and make inlining optimisations.

Is this correct? Thank you.

Xeo
  • 129,499
  • 52
  • 291
  • 397
Steve
  • 8,153
  • 9
  • 44
  • 91
  • 4
    There is no overhead when using a lambda expression. The other choice would be to write such a function object yourself, which would exactly the same. Btw, on the inline question, since the compiler has all information it needs, it sure can just inline the call to the `operator()`. There is no "lifting" to be done, lambdas aren't anything special. They're just a short-hand for a local function object. – Xeo Aug 30 '12 at 17:58
  • This seems to be a question about if `std::function` stores its state on the heap or not, and has nothing to do with lambdas. Is that right? – Mooing Duck Aug 30 '12 at 18:06
  • 12
    Just to spell it out in case of any misunderstandings: **A lambda expression is *not* a `std::function`!!** – Xeo Aug 30 '12 at 18:18
  • 1
    Just a side comment: be careful when returning a lambda from a function, since any local variables captured by reference become invalid after leaving the function that has created the lambda. – Giorgio Aug 30 '12 at 19:03
  • Okay, std::function!=lambda, but isn't it the only way to return a closure from a function? I could be mistaken about that. – Steve Aug 30 '12 at 22:47
  • 2
    @Steve since C++14 you can return a lambda from a function with an `auto` return type. – Oktalist Dec 03 '18 at 12:17

2 Answers2

123

My current understanding is that a lambda with no captured closure is exactly like a C callback. However, when the environment is captured either by value or by reference, an anonymous object is created on the stack.

No; it is always a C++ object with an unknown type, created on the stack. A capture-less lambda can be converted into a function pointer (though whether it is suitable for C calling conventions is implementation dependent), but that doesn't mean it is a function pointer.

When a value-closure must be returned from a function, one wraps it in std::function. What happens to the closure memory in this case?

A lambda isn't anything special in C++11. It's an object like any other object. A lambda expression results in a temporary, which can be used to initialize a variable on the stack:

auto lamb = []() {return 5;};

lamb is a stack object. It has a constructor and destructor. And it will follow all of the C++ rules for that. The type of lamb will contain the values/references that are captured; they will be members of that object, just like any other object members of any other type.

You can give it to a std::function:

auto func_lamb = std::function<int()>(lamb);

In this case, it will get a copy of the value of lamb. If lamb had captured anything by value, there would be two copies of those values; one in lamb, and one in func_lamb.

When the current scope ends, func_lamb will be destroyed, followed by lamb, as per the rules of cleaning up stack variables.

You could just as easily allocate one on the heap:

auto func_lamb_ptr = new std::function<int()>(lamb);

Exactly where the memory for the contents of a std::function goes is implementation-dependent, but the type-erasure employed by std::function generally requires at least one memory allocation. This is why std::function's constructor can take an allocator.

Is it freed whenever the std::function is freed, i.e., is it reference-counted like a std::shared_ptr?

std::function stores a copy of its contents. Like virtually every standard library C++ type, function uses value semantics. Thus, it is copyable; when it is copied, the new function object is completely separate. It is also moveable, so any internal allocations can be transferred appropriately without needing more allocating and copying.

Thus there is no need for reference counting.

Everything else you state is correct, assuming that "memory allocation" equates to "bad to use in real-time code".

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
  • 1
    Excellent explanation, thank you. So the creation of `std::function` is the point at which the memory is allocated and copied. It seems to follow that there is no way to return a closure (since they are allocated on the stack), without first copying into a `std::function`, yes? – Steve Aug 31 '12 at 03:38
  • 3
    @Steve: Yes; you have to wrap a lambda in some kind of container in order for it to exit the scope. – Nicol Bolas Aug 31 '12 at 09:20
  • Is the entire function's code copied, or is the original function compile-time-allocated and passed the closed-over values? – Llamageddon May 10 '15 at 09:33
  • I want to add that the standard more or less indirectly mandates (§ 20.8.11.2.1 [func.wrap.func.con] ¶ 5) that if a lambda does not capture anything, it can be stored in a `std::function` object without dynamic memory allocation going on. – 5gon12eder Oct 16 '15 at 12:59
  • I may be late, but you can return a lambda without wrapping it, juste use auto as return type. Something like "aute getLambda(){ return [](){cout << "Hello world !";};} – bisthebis Jul 03 '16 at 13:10
  • @bisthebis: You should take note of the date when that comment was made. – Nicol Bolas Jul 03 '16 at 13:11
  • Correction to "generally requires at least one memory allocation" may be in order. I was under the impression that small enough `std::function` where mandated to not throw, and hence cannot do a memory allocation. I do not know if that mandate was in force in C++11, but even in C++11 it is clearly possible to do type erasure without allocating memory, unless C++11 forced it somehow. – Yakk - Adam Nevraumont Aug 22 '16 at 14:56
  • @Yakk:: The standard only requires that it not allocate memory if the given callable is "passed via `reference_wrapper` or a function pointer." So even with a member pointer, you are not *guaranteed* not to allocate memory. – Nicol Bolas Aug 22 '16 at 15:00
  • @NicolBolas Ok; but "generally requires at least one memory allocation" isn't true, or is at least misleading. It can do a memory allocation, but it is only generally *required* for large objects (are you aware of an implementation that allocates when passed an object with a pointer or less worth of state?) To be concrete, I would not expect any heap allocations for any of the lambdas the OP included in the question if stored in a `std::function` on any compiler I work on; I could be wrong. – Yakk - Adam Nevraumont Aug 22 '16 at 15:09
  • 2
    @Yakk: How do you define "large"? Is an object with two pointers' of state "large"? How about 3 or 4? Also, object size is not the only issue; if the object isn't nothrow-moveable, it *must* be stored in an allocation, since `function` has a noexcept move constructor. The whole point of saying "generally requires" is that I'm not saying "*always* requires": that there are circumstances where no allocation will be performed. – Nicol Bolas Aug 22 '16 at 15:57
  • 1
    Claiming that lambdas are "created on the stack" is too aggressive. Firstly, lambda objects are temporaries, meaning that it is unspecified where they are created. Secondly, lambda expressions are allowed in non-local scopes, which means that stack is not necessarily a viable memory source for them. – AnT stands with Russia Sep 30 '16 at 05:16
  • If lambda expressions have the same scoping rules as regular variables (and they're allocated on the stack) does that mean that attaching lambdas to [signals in Qt](https://wiki.qt.io/New_Signal_Slot_Syntax) is okay because the lambda is uh.. passed by value into `connect` and copied? – jrh Jun 12 '20 at 13:33
5

C++ lambda is just a syntactic sugar around (anonymous) Functor class with overloaded operator() and std::function is just a wrapper around callables (i.e functors, lambdas, c-functions, ...) which does copy by value the "solid lambda object" from the current stack scope - to the heap.

To test the number of actual constructors/relocatons I made a test (using another level of wrapping to shared_ptr but its not the case). See for yourself:

#include <memory>
#include <string>
#include <iostream>

class Functor {
    std::string greeting;
public:

    Functor(const Functor &rhs) {
        this->greeting = rhs.greeting;
        std::cout << "Copy-Ctor \n";
    }
    Functor(std::string _greeting="Hello!"): greeting { _greeting } {
        std::cout << "Ctor \n";
    }

    Functor & operator=(const Functor & rhs) {
        greeting = rhs.greeting;
        std::cout << "Copy-assigned\n";
        return *this;
    }

    virtual ~Functor() {
        std::cout << "Dtor\n";
    }

    void operator()()
    {
        std::cout << "hey" << "\n";
    }
};

auto getFpp() {
    std::shared_ptr<std::function<void()>> fp = std::make_shared<std::function<void()>>(Functor{}
    );
    (*fp)();
    return fp;
}

int main() {
    auto f = getFpp();
    (*f)();
}

it makes this output:

Ctor 
Copy-Ctor 
Copy-Ctor 
Dtor
Dtor
hey
hey
Dtor

Exactly same set of ctors/dtors would be called for the stack-allocated lambda object! (Now it calls Ctor for stack allocation, Copy-ctor (+ heap alloc) to construct it in std::function and another one for making shared_ptr heap allocation + construction of function)

barney
  • 2,172
  • 1
  • 16
  • 25