C++11 lambda 实现和内存模型
我想了解如何正确思考 C++11 闭包和 std::function
的实现方式和内存处理方式.
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.
因此,我想更好地了解何时使用或不使用 C++ lambda.
Therefore I'd like to develop a better understanding of when to use or not use C++ lambdas.
我目前的理解是,没有捕获闭包的 lambda 与 C 回调完全一样.但是,当通过值或引用捕获环境时,会在堆栈上创建一个匿名对象.当必须从函数返回值闭包时,可以将其包装在 std::function
中.在这种情况下,闭包内存会发生什么?是否从栈复制到堆?每当 std::function
被释放时它是否被释放,即它是否像 std::shared_ptr
一样被引用计数?
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
?
我想象在一个实时系统中,我可以建立一个 lambda 函数链,将 B 作为继续参数传递给 A,从而创建一个处理管道 A->B
.在这种情况下,A 和 B 闭包将被分配一次.虽然我不确定这些是分配在堆栈上还是堆上.但是总的来说,这在实时系统中使用似乎是安全的.另一方面,如果 B 构造了一些 lambda 函数 C 并返回它,那么 C 的内存将被重复分配和释放,这对于实时使用来说是不可接受的.
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.
在伪代码中,一个 DSP 循环,我认为这将是实时安全的.我想执行处理块 A 然后 B,其中 A 调用它的参数.这两个函数都返回 std::function
对象,所以 f
将是一个 std::function
对象,它的环境存储在堆上:
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.
这是正确的吗?谢谢.
推荐答案
我目前的理解是,没有捕获闭包的 lambda 与 C 回调完全一样.但是,当环境通过值或引用捕获时,会在堆栈上创建一个匿名对象.
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.
没有;它始终是一个在堆栈上创建的未知类型的 C++ 对象.无捕获的 lambda 可以转换为函数指针(尽管它是否适合 C 调用约定取决于实现),但这并不意味着它是函数指针.
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.
当必须从函数返回值闭包时,将其包装在 std::function 中.在这种情况下,闭包内存会发生什么变化?
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?
lambda 在 C++11 中没有什么特别之处.它是一个像任何其他对象一样的对象.一个 lambda 表达式产生一个临时的,可用于初始化堆栈上的变量:
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
是一个堆栈对象.它有一个构造函数和析构函数.它将遵循所有的 C++ 规则.lamb
的类型将包含捕获的值/引用;它们将成为该对象的成员,就像任何其他类型的任何其他对象成员一样.
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.
你可以把它给一个 std::function
:
auto func_lamb = std::function<int()>(lamb);
在这种情况下,它将获得lamb
值的副本.如果 lamb
按值捕获了任何内容,则这些值将有两个副本;一个在 lamb
中,一个在 func_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
.
当当前作用域结束时,func_lamb
将被销毁,然后是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);
std::function
内容的确切内存去向取决于实现,但 std::function
使用的类型擦除通常需要在至少一个内存分配.这就是 std::function
的构造函数可以接受分配器的原因.
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.
每当 std::function 被释放时它是否被释放,即它是否像 std::shared_ptr 一样被引用计数?
Is it freed whenever the std::function is freed, i.e., is it reference-counted like a std::shared_ptr?
std::function
存储其内容的副本.与几乎所有标准库 C++ 类型一样,function
使用 值语义.因此,它是可复制的;当它被复制时,新的 function
对象是完全独立的.它也是可移动的,因此可以适当地转移任何内部分配,而无需进行更多的分配和复制.
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.
因此不需要引用计数.
您陈述的所有其他内容都是正确的,假设内存分配"等同于在实时代码中使用不好".
Everything else you state is correct, assuming that "memory allocation" equates to "bad to use in real-time code".
相关文章