c++ 11 lambda是如何表示和传递的
How are C++11 lambdas represented and passed?
在c++11中传递lambda非常容易:
func( []( int arg ) {
// code
} ) ;
但我想知道,传递一个lambda给这样的函数的成本是什么?如果函数将lambda传递给其他函数呢?
void func( function< void (int arg) > f ) {
doSomethingElse( f ) ;
}
传递lambda代价大吗?由于function
对象可以赋值为0,因此
function< void (int arg) > f = 0 ; // 0 means "not init"
让我觉得函数对象的行为有点像指针。但如果不使用new
,则意味着它们可能类似于值类型的struct
或类,后者默认为堆栈分配和成员复制。
如何是一个c++ 11"代码体"和组捕获的变量传递当你传递一个函数对象"按值"?是否存在大量多余的代码体副本?我是否应该用const&
标记传递的每个function
对象,这样就不会产生副本:
void func( const function< void (int arg) >& f ) {
}
或者函数对象以某种方式传递不同于常规c++结构?
免责声明:与现实相比,我的回答有些简化(我把一些细节放在一边),但大局在这里。此外,标准并没有完全规定lambdas或std::function
必须如何在内部实现(实现有一定的自由),因此,就像任何关于实现细节的讨论一样,您的编译器可能会或可能不会完全这样做。
但是,这是一个非常类似于VTables的主题:标准没有强制要求太多,但任何明智的编译器仍然很可能这样做,所以我认为值得深入研究一下。:)
<标题>λ
实现lambda最直接的方法是一种未命名的struct
:
auto lambda = [](Args...) -> Return { /*...*/ };
// roughly equivalent to:
struct {
Return operator ()(Args...) { /*...*/ }
}
lambda; // instance of the unnamed struct
就像任何其他类一样,当您传递它的实例时,您不必复制代码,只需复制实际数据(在这里,根本没有)。
按值捕获的对象被复制到struct
:
Value v;
auto lambda = [=](Args...) -> Return { /*... use v, captured by value...*/ };
// roughly equivalent to:
struct Temporary { // note: we can't make it an unnamed 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
再次强调,传递它只意味着传递数据(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
这几乎就是关于lambda本身的所有内容(除了我提交的一些实现细节,但这些细节与理解它的工作原理无关)。
std::function
std::function
是任何类型的函子(lambda,独立/静态/成员函数,像我展示的那些函子类,…)的通用包装器。
std::function
的内部非常复杂,因为它们必须支持所有这些情况。根据函子的确切类型,这至少需要以下数据(给出或取实现细节):
- 指向独立/静态函数的指针。
或
- 指向函子副本的指针(动态分配,允许任何类型的函子,正如您正确注意到的)。
- 指向要调用的成员函数的指针。
- 指向分配器的指针,该分配器既可以复制函子,也可以复制自身(因为可以使用任何类型的函子,指向函子的指针应该是
void*
),因此必须有这样一种机制——可能使用多态性,即。基类+虚方法,派生类在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
因此,当您传递std::function
时,它至少涉及这四个指针(实际上在GCC 4.7 64位sizeof(std::function<void()>
是32,即四个64位指针)和可选的动态分配的函子副本(正如我已经说过的,仅包含捕获的对象,您不复制代码)。
对问题的回答
将lambda传递给这样的函数的代价是什么?<一口>[背景问题:价值] 一口>
嗯,正如你所看到的,它主要取决于你的函子(手工制作的struct
函子或lambda)和它包含的变量。与直接按值传递struct
函子相比,开销可以忽略不计,但它当然比按引用传递struct
函子要高得多。
恐怕这个问题很难用一般的方法来回答。有时你想通过我应该用
const&
标记传递的每个函数对象,以便不进行复制吗?
const
引用传递,有时通过值传递,有时通过rvalue引用传递,这样你就可以移动它。这取决于你代码的语义。
在我看来,关于你应该选择哪一个的规则是完全不同的话题,只要记住它们和任何其他对象是一样的。
无论如何,您现在拥有了做出明智决策的所有关键(同样,取决于您的代码及其语义)。
标题>
参见c++ 11 lambda实现和内存模型
lambda表达式就是一个表达式。一旦编译,它将在运行时产生一个闭包对象。
5.1.2 Lambda表达式(expr.prim.lambda)
lambda表达式的求值导致一个右值临时值(12.2)。这个临时对象称为闭包对象。
对象本身是实现定义的,在不同的编译器中可能不同。
这是lambdas在clang中的原始实现https://github.com/faisalv/clang-glambda
如果lambda可以作为一个简单的函数(即它不捕获任何东西),那么它是完全相同的方式。特别是标准要求它与具有相同签名的旧式指针函数兼容。[编辑:不准确,见评论讨论]
其余的取决于实现,但我不会提前担心。最直接的实现除了携带信息之外什么都不做。和你在抓捕时要求的一样多。所以效果和你手工创建一个类是一样的。或者使用std::bind的变体
- 表示"accepting anything for this template argument" C++概念的通配符
- lambda参数转换为constexpr技巧,然后获取带链接的数组
- 可组合的lambda/std::函数与std::可选
- 如何将ampl中的集合表示为c++中的向量
- std::is_base_of表示ctor编译错误
- C++Boost Asio Pool线程,带有lambda函数和传递引用变量
- 如何建立使用模板函数的lambda函数的尾部返回类型
- 输入中的字符串数未知(以字母表示)
- 我可以信任表示整数的浮点或双精度来保持精度吗
- c++模板来表示多项式
- 如何将lambda作为模板类的成员函数参数
- C++从其他 constexpr 创建 lambda 不能按顺序执行 Constexpr
- 在 lambda 捕获中声明的变量的类型推导
- 我可以将调用类的"this"传递给 lambda 函数吗?
- 为什么lambda在clang上崩溃而不是在gcc上崩溃
- 模板函数指针和lambda
- 询问在设计我的手臂模拟器功能表示格式1
- 类方法接受表示各种参数数函数的 lambda
- 在 C++ 中用什么来表示 lambda 字符
- c++ 11 lambda是如何表示和传递的