c++ lambda capture by value 的实验
c++ 中 lambda 的类型和大小 到 lambda 生成一个匿名类。当没有 capture 任何变量的时候,大小几乎是 0 。
c++11 中的 lambda 关键字对于程序的结构影响很大,其中的闭包是一个很关键 的概念。因为 c++ 语言本身不支持垃圾回收,所以 capture by value 和其他 语言还是有区别的。关键的问题在于,拷贝构造函数调用多少次?
例如代码
#include <iostream>
#include <functional>
#include <boost/type_index.hpp>
using cnamespace std;
class Foo {
public:
Foo():i(100) {
cout << __FILE__ << ":" << __LINE__ << ": [" << __PRETTY_FUNCTION__<< "] "
<< "i " << i << " "
<< endl;
}
Foo(const Foo& foo):i(foo.i + 1) {
cout << __FILE__ << ":" << __LINE__ << ": [" << __PRETTY_FUNCTION__<< "] "
<< "i " << i << " "
<< endl;
}
~Foo() {
cout << __FILE__ << ":" << __LINE__ << ": [" << __PRETTY_FUNCTION__<< "] "
<< "i " << i << " "
<< endl;
i = -100;
}
public:
int i = 0;
friend ostream& operator<<(ostream& out, const Foo & obj);
};
ostream& operator<<(ostream& out, const Foo & obj)
{
out << "foo(" << &obj << "," << obj.i << ")";
return out;
}
void
CreateLambda() {
Foo foo;
{
[foo]() { cout << foo.i << endl; }();
}
}
int main(int argc, char *argv[])
{
CreateLambda();
return 0;
}
程序的输出如下
cpp_lambda_capture_1.cpp:8: [Foo::Foo()] i 100
before create lambda1
cpp_lambda_capture_1.cpp:13: [Foo::Foo(const Foo &)] i 101
after create lambda1
before create lambda2
cpp_lambda_capture_1.cpp:13: [Foo::Foo(const Foo &)] i 102
after create lambda2
before create lambda3
cpp_lambda_capture_1.cpp:13: [Foo::Foo(const Foo &)] i 103
after create lambda3
before create lambda3
cpp_lambda_capture_1.cpp:13: [Foo::Foo(const Foo &)] i 104
after create lambda4
before lambda4 out of scope
cpp_lambda_capture_1.cpp:18: [Foo::~Foo()] i 104
after lambda4 out of scope
cpp_lambda_capture_1.cpp:18: [Foo::~Foo()] i 103
after lambda3 out of scope
before lambda2 out of scope
cpp_lambda_capture_1.cpp:18: [Foo::~Foo()] i 102
after lambda2 out of scope
cpp_lambda_capture_1.cpp:18: [Foo::~Foo()] i 101
cpp_lambda_capture_1.cpp:18: [Foo::~Foo()] i 100
我们仔细看输出,可以看到一下几点
- 生成的匿名 lambda 类,内部有一个不可见的成员变量
Foo
。 - 匿名 lambda 对象赋值的时候,会调用内部成员变量变量的
Foo
的拷贝构造函数。 - 匿名 lambda 对象析构的时候,会调用内部成员变量的析构函数,析构掉这个不可见的成员变量。
我们看看生成对象的大小
例如代码
#include <iostream>
#include <functional>
#include <boost/type_index.hpp>
#include <iostream>
#include <functional>
#include <boost/type_index.hpp>
using namespace std;
class Foo {
public:
Foo():i(100) {
cout << __FILE__ << ":" << __LINE__ << ": [" << __PRETTY_FUNCTION__<< "] "
<< "i " << i << " "
<< endl;
}
Foo(const Foo& foo):i(foo.i + 1) {
cout << __FILE__ << ":" << __LINE__ << ": [" << __PRETTY_FUNCTION__<< "] "
<< "i " << i << " "
<< endl;
}
~Foo() {
cout << __FILE__ << ":" << __LINE__ << ": [" << __PRETTY_FUNCTION__<< "] "
<< "i " << i << " "
<< endl;
i = -100;
}
public:
int i = 0;
friend ostream& operator<<(ostream& out, const Foo & obj);
};
ostream& operator<<(ostream& out, const Foo & obj)
{
out << "foo(" << &obj << "," << obj.i << ")";
return out;
}
template<typename T>
void show_type_and_size(T& x) {
cout << "T = "
<< boost::typeindex::type_id_with_cvr<T>().pretty_name() << ";\n"
<< boost::typeindex::type_id_with_cvr<decltype(x)>().pretty_name() << " x;\n"
<< "sizeof(x) " << sizeof(x) << "\n"
<< endl;
}
void
CreateLambda() {
Foo foo;
auto lambda1 = [foo](){ cout << foo.i << endl;};
show_type_and_size(foo);
show_type_and_size(lambda1);
}
int main(int argc, char *argv[])
{
CreateLambda();
return 0;
}
程序的输出如下
cpp_lambda_capture_2.cpp:12: [Foo::Foo()] i 100
cpp_lambda_capture_2.cpp:17: [Foo::Foo(const Foo &)] i 101
T = Foo;
Foo& x;
sizeof(x) 4
T = CreateLambda()::$_0;
CreateLambda()::$_0& x;
sizeof(x) 4
cpp_lambda_capture_2.cpp:22: [Foo::~Foo()] i 101
cpp_lambda_capture_2.cpp:22: [Foo::~Foo()] i 100
可以看到,对象的大小就是 4 字节 ,和 Foo 本身的对象是一样大的。这说明
- 除了抓住的对象的内存,匿名 lambda 类本身不占用额外的内存
- 匿名 lambda 类没有虚函数表
这里注意到一点
std::function<void(void)> f = [](){...};
auto lambda = [](){...};
这两个是不一样的, f
是一个 std::function
,内部可以包含一个
lambda 对象。会调用 lambda 对象的拷贝构造函数,构造一个新的 lambda 对
象。于是,capture by value 的 lambda 对象,也会调用每一个 capture 对象
的拷贝构造函数。