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 

我们仔细看输出,可以看到一下几点

  1. 生成的匿名 lambda 类,内部有一个不可见的成员变量 Foo
  2. 匿名 lambda 对象赋值的时候,会调用内部成员变量变量的 Foo 的拷贝构造函数。
  3. 匿名 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 本身的对象是一样大的。这说明

  1. 除了抓住的对象的内存,匿名 lambda 类本身不占用额外的内存
  2. 匿名 lambda 类没有虚函数表

这里注意到一点

std::function<void(void)> f = [](){...};
auto lambda = [](){...};

这两个是不一样的, f 是一个 std::function ,内部可以包含一个 lambda 对象。会调用 lambda 对象的拷贝构造函数,构造一个新的 lambda 对 象。于是,capture by value 的 lambda 对象,也会调用每一个 capture 对象 的拷贝构造函数。