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 对象
的拷贝构造函数。