[英]C++11 lambda implementation and memory model
我想了解一些关于如何正确思考C ++ 11闭包和std::function
,包括如何实现它们以及如何处理内存。
虽然我不相信过早优化,但我确实习惯在编写新代码时仔细考虑我的选择对性能的影响。 我还进行了大量的实时编程,例如微控制器和音频系统,其中要避免非确定性的存储器分配/解除分配暂停。
因此,我想更好地了解何时使用或不使用C ++ lambdas。
我目前的理解是没有捕获闭包的lambda就像一个C回调。 但是,当通过值或引用捕获环境时,将在堆栈上创建匿名对象。 当必须从函数返回值闭包时,将其包装在std::function
。 在这种情况下闭包内存会发生什么? 它是从堆栈复制到堆? 每当释放std::function
时它是否被释放,即它是否像std::shared_ptr
一样被引用计数?
我想在实时系统中我可以建立一个lambda函数链,将B作为一个连续参数传递给A,这样就可以创建一个处理管道A->B
在这种情况下,A和B闭包将被分配一次。 虽然我不确定这些是否会在堆栈或堆上分配。 然而,一般来说,这在实时系统中使用似乎是安全的。 另一方面,如果B构造一些它返回的lambda函数C,那么C的内存将被重复分配和释放,这对于实时使用是不可接受的。
在伪代码中,一个DSP循环,我认为这将是实时安全的。 我想执行处理块A然后执行B,其中A调用它的参数。 这两个函数都返回std::function
对象,因此f
将是一个std::function
对象,其环境存储在堆上:
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)
}
我认为在实时代码中使用它可能很糟糕:
for (t=0; t<1000; t++) {
y = A(B)(t);
}
我认为堆栈内存很可能用于闭包:
freq = 220;
A = 2;
for (t=0; t<1000; t++) {
y = [=](int t){ return sin(t*freq)*A; }
}
在后一种情况下,闭包是在循环的每次迭代中构造的,但与前面的例子不同,它很便宜,因为它就像一个函数调用,没有进行堆分配。 此外,我想知道编译器是否可以“解除”关闭并进行内联优化。
它是否正确? 谢谢。
我目前的理解是没有捕获闭包的lambda就像一个C回调。 但是,当通过值或引用捕获环境时,将在堆栈上创建匿名对象。
没有; 它总是一个在堆栈上创建的具有未知类型的C ++对象。 无捕获的lambda可以转换为函数指针(尽管它是否适合C调用约定依赖于实现),但这并不意味着它是一个函数指针。
当必须从函数返回值闭包时,将其包装在std :: function中。 在这种情况下闭包内存会发生什么?
lambda在C ++ 11中并不特别。 这是一个像任何其他对象一样的对象。 lambda表达式导致临时,可用于初始化堆栈上的变量:
auto lamb = []() {return 5;};
lamb
是一个堆栈对象。 它有一个构造函数和析构函数。 它将遵循所有C ++规则。 lamb
的类型将包含捕获的值/引用; 它们将成为该对象的成员,就像任何其他类型的任何其他对象成员一样。
你可以把它交给std::function
:
auto func_lamb = std::function<int()>(lamb);
在这种情况下,它将获得lamb
值的副本 。 如果lamb
按价值捕获了任何东西,那么这些价值就会有两个副本; 一个在lamb
,一个在func_lamb
。
当前作用域结束时,根据清理堆栈变量的规则,将销毁func_lamb
,然后是lamb
。
您可以轻松地在堆上分配一个:
auto func_lamb_ptr = new std::function<int()>(lamb);
确切地说, std::function
内容的内存依赖于实现,但std::function
使用的类型擦除通常至少需要一次内存分配。 这就是std::function
的构造函数可以使用分配器的原因。
每当释放std :: function时它是否被释放,即它是否像std :: shared_ptr一样被引用计数?
std::function
存储其内容的副本 。 与几乎每个标准库C ++类型一样, function
使用值语义 。 因此,它是可复制的; 复制时,新的function
对象是完全独立的。 它也是可移动的,因此可以适当地传输任何内部分配,而无需更多的分配和复制。
因此,不需要参考计数。
假设“内存分配”等同于“在实时代码中使用不好”,那么您声明的其他所有内容都是正确的。
C ++ lambda只是一个语法糖(匿名)Functor类,带有重载的operator()
和std::function
只是一个包含在callables(即functor,lambdas,c-functions,...)的包装器,它按值复制 “实体lambda对象“从当前堆栈范围 - 到堆 。
为了测试实际构造函数/重定位器的数量,我进行了测试(使用另一级别的包装到shared_ptr,但事实并非如此)。 你自己看:
#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)();
}
它使这个输出:
Ctor
Copy-Ctor
Copy-Ctor
Dtor
Dtor
hey
hey
Dtor
对于堆栈分配的lambda对象,将调用完全相同的ctors / dtors集合! (现在它调用Ctor进行堆栈分配,Copy-ctor(+ heap alloc)在std :: function中构造它,另一个用于进行shared_ptr堆分配+构造函数)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.