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最直接的方法是匿名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::functionstd::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,只要记住它们与任何其他对象相同.
无论如何,您现在拥有做出明智决定的所有密钥(同样,取决于您的代码及其语义).