如何表示和传递C++ 11 lambdas?

bob*_*obo 30 c++ lambda parameter-passing c++11 std-function

在c ++ 11中传递lambda非常简单:

func( []( int arg ) {
  // code
} ) ;
Run Code Online (Sandbox Code Playgroud)

但我想知道,将lambda传递给像这样的函数的成本是多少?如果func将lambda传递给其他函数怎么办?

void func( function< void (int arg) > f ) {
  doSomethingElse( f ) ;
}
Run Code Online (Sandbox Code Playgroud)

lambda的传递是否昂贵?由于可以function对象分配0,

function< void (int arg) > f = 0 ; // 0 means "not init" 
Run Code Online (Sandbox Code Playgroud)

它让我认为函数对象就像指针一样.但是,如果不使用new,则意味着它们可能类似于值类型struct或类,它们默认为堆栈分配和成员方式副本.

当你按"值"传递一个函数对象时,C++ 11"代码体"和捕获的变量组是如何传递的?是否有很多代码体的多余副本?我是否必须标记function传递的每个对象,const&以便不进行复制:

void func( const function< void (int arg) >& f ) {
}
Run Code Online (Sandbox Code Playgroud)

或者以某种方式使函数对象以不同于常规C++结构的方式传递?

sya*_*yam 32

免责声明:与现实相比,我的答案有所简化(我把一些细节放在一边),但大局就在这里.此外,标准没有完全指定lambda如何或std::function必须在内部实现(实现有一些自由),所以,就像任何关于实现细节的讨论一样,你的编译器可能会也可能不会这样做.

但同样,这是一个与VTables非常类似的主题:标准并没有强制要求,但任何合理的编译器仍然很可能这样做,所以我认为值得深入研究它.:)


Lambda表达式

实现lambda最直接的方法是匿名struct:

auto lambda = [](Args...) -> Return { /*...*/ };

// roughly equivalent to:
struct {
    Return operator ()(Args...) { /*...*/ }
}
lambda; // instance of the anonymous struct
Run Code Online (Sandbox Code Playgroud)

就像任何其他类一样,当你传递它的实例时,你永远不必复制代码,只需要复制实际数据(这里根本没有).


按值捕获的对象将复制到struct:

Value v;
auto lambda = [=](Args...) -> Return { /*... use v, captured by value...*/ };

// roughly equivalent to:
struct Temporary { // note: we can't make it an anonymous struct any more since we need
                   // a constructor, but that's just a syntax quirk

    const Value v; // note: capture by value is const by default unless the lambda is mutable
    Temporary(Value v_) : v(v_) {}
    Return operator ()(Args...) { /*... use v, captured by value...*/ }
}
lambda(v); // instance of the struct
Run Code Online (Sandbox Code Playgroud)

再次,传递它只意味着您传递data(v)而不是代码本身.


同样,通过引用捕获的对象被引用到struct:

Value v;
auto lambda = [&](Args...) -> Return { /*... use v, captured by reference...*/ };

// roughly equivalent to:
struct Temporary {
    Value& v; // note: capture by reference is non-const
    Temporary(Value& v_) : v(v_) {}
    Return operator ()(Args...) { /*... use v, captured by reference...*/ }
}
lambda(v); // instance of the struct
Run Code Online (Sandbox Code Playgroud)

这几乎就是lambdas本身(除了我省略的几个实现细节,但与理解它是如何工作无关).


std::function

std::function 是任何类型的仿函数的通用包装器(lambdas,独立/静​​态/成员函数,像我展示的仿函数类,......).

内部std::function结构非常复杂,因为它们必须支持所有这些情况.根据仿函数的确切类型,这至少需要以下数据(给出或采取实现细节):

  • 指向独立/静态函数的指针.

要么,

  • 指向仿函数的副本[请参阅下面的注释](动态分配以允许任何类型的仿函数,正如您正确指出的那样).
  • 指向要调用的成员函数的指针.
  • 一个指向分配器的指针,它既可以复制仿函数又可以自己(因为可以使用任何类型的仿函数,指针到仿函数应该是void*,因此必须有这样的机制 - 可能使用多态而已. class + virtual方法,派生类在template<class Functor> function(Functor)构造函数中本地生成).

由于它事先不知道它将要存储哪种类型的仿函数(并且这std::function可以通过可以重新分配的事实显而易见),因此它必须处理所有可能的情况并在运行时做出决定.

注意:我不知道标准要求它在哪里,但这肯定是一个新副本,底层仿函数不共享:

int v = 0;
std::function<void()> f = [=]() mutable { std::cout << v++ << std::endl; };
std::function<void()> g = f;

f(); // 0
f(); // 1
g(); // 0
g(); // 1
Run Code Online (Sandbox Code Playgroud)

因此,当你传递std::function它时至少涉及那四个指针(实际上在GCC 4.7 64位sizeof(std::function<void()>是32 位,这是四个64位指针)和可选的动态分配的仿函数副本(正如我已经说过的那样,只包含捕获的对象,你不复制代码).


回答问题

将lambda传递给像这样的函数的成本是多少?[问题的背景:按价值 ]

好吧,你可以看到它主要取决于你的仿函数(手工struct编写器或lambda)及其包含的变量.的开销相比,直接传递struct按值函子是很微不足道的,但它比传递一个当然要高得多struct引用仿函数.

我是否必须标记传递的每个函数对象,const&以便不进行复制?

我担心这很难以通用方式回答.有时您会希望通过const引用传递,有时通过值传递,有时通过右值引用传递,以便您可以移动它.它实际上取决于代码的语义.

关于你应该选择哪一个的规则是完全不同的主题IMO,只要记住它们与任何其他对象相同.

无论如何,您现在拥有做出明智决定的所有密钥(同样,取决于您的代码及其语义).