C ++ 11 implementation and memory model

I need some information on how to think about C ++ 11 and std::function closures correctly, how they are implemented and how memory is handled.

Despite the fact that I do not believe in premature optimization, I still have the habit of carefully analyzing the impact of my decisions when writing new code. I also do quite a lot of real-time programming, for example. on microcontrollers and in audio systems where periods of non-allocation of memory / freeing of memory should be avoided.

So I would like to better understand when to use or not to use C ++ lambdas.

My real understanding is that a lambda without a captured closure exactly resembles 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 closure value is to be returned from a function, one wraps it in std::function . What happens to the closing memory in this case? Is it copied from stack to heap? Does it get freed whenever std::function freed, i.e. Is it considered a reference as a std::shared_ptr ?

I assume that in a real-time system, I could create a chain of lambda functions by passing B as an argument to continue A, so that the processing pipeline A->B . In this case, the closures A and B will be highlighted once. Although I'm not sure if they will be allocated on the stack or heap. However, overall it seems safe to use in a real-time system. On the other hand, if B creates some lambda function C that it returns, then the memory for C will be allocated and freed up again, which is unacceptable for use in real time.

In the pseudo-code, the DSP loop, which I think will be safe in real time. I want to execute processing block A and then B, where A calls its argument. Both of 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 that I think may be bad for real-time use:

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

And the one where, I think, the stack stack is probably used to close:

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

In the latter case, closure is built on each iteration of the loop, but, unlike the previous example, it is cheap, because it is like a function call, no heap allocations are made. Moreover, I am wondering if the compiler can β€œraise” the closure and optimize the attachment.

It is right? Thank.

+81
c ++ lambda memory c ++ 11
Aug 30 '12 at 17:50
source share
2 answers

My real understanding is that a lambda without a captured closure exactly resembles 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. Without a capture, a lambda can be converted to a pointer to a function (although it is suitable for C calling conventions, it depends on the implementation), but this does not mean that it is a pointer to a function.

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

A lambda is not something special in C ++ 11. It is an object, like any other object. The lambda expression leads to a temporary one, which can be used to initialize the variable on the stack:

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

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

You can pass it to std::function :

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

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

When the current scope expires, func_lamb will be destroyed and then lamb , according to the rules for clearing stack variables.

You can 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 std::function goes depends on the implementation, but the type erasure used by std::function usually requires at least one memory allocation. This is why the std::function constructor can take a dispenser.

It is freed whenever the std :: function is freed, i.e. Does it refer to std :: shared_ptr?

std::function is a copy of its contents. Like almost every standard C ++ library type, function uses value semantics. Thus, it can be copied; when copying, the new function object is completely split. It can also be moved, so any internal distributions can be transferred accordingly, without requiring more selection and copying.

Thus, there is no need for reference counting.

Everything else that you state is correct if you assume that "memory allocation" corresponds to "poor use in real time."

+85
Aug 30 2018-12-18T00:
source share

C ++ lambda is just syntactic sugar around (anonymous). A Functor class with operator() and std::function overloaded is just a wrapper around called objects (i.e. functors, lambdas, c-functions, ...) that copy by value a "solid lambda object" from the current area stack - in a bunch .

To check the number of actual constructors / relocatons, I did a test (using a different wrapping level for shared_ptr, but that is not the case). Look at 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)(); } 

he draws this conclusion:

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

Exactly the same set of ctors / dtors is called for a stacked lambda object! (Now he calls Ctor to distribute the stack, Copy-ctor (+ heap alloc) to build it in std :: function, and the other to create the heap distribution shared_ptr + build the function)

0
Dec 03 '18 at 11:20
source share



All Articles