如何表示和传递C ++ 11 lambdas? [英] How are C++11 lambdas represented and passed?
问题描述
在c ++ 11中传递lambda非常容易:
func([ $ b // code
});
但我想知道,传递lambda的费用是多少到这样的功能?如果func将lambda传递给其他函数,该怎么办?
void func(function< void b $ b doSomethingElse(f);
}
lambda的传递是否昂贵?由于函数
对象可以赋值为0,
function< void(int arg)> f = 0; // 0表示不是init
它让我认为函数对象的行为像指针。但是没有使用 new
,那么它们意味着它们可能类似于值类型 struct
或类,它默认为stack
当你按value传递一个函数对象时,如何传递一个C ++ 11代码主体和一组捕获的变量?有很多的代码体的多余的副本吗?我必须标记通过 const&
传递的每个函数
对象,以便不进行复制:
void func(const function< void(int arg)>& f){
}
或者函数对象以不同于常规C ++结构的方式传递?
免责声明:我的答案有点简化了与现实(我把一些细节放在一边),但大的图景在这里。此外,标准没有完全指定如何lambdas或
std :: function
必须在内部实现(实现有一些自由),所以,像任何讨论的实现细节,你的编译器可能也可能不会这样做。
但是,这是一个非常类似于VTables的主题:标准不需要太多,但任何明智的编译器仍然很可能这样做,所以我相信它值得挖一点。 :)
Lambdas
一个lambda是一种匿名的
struct
:auto lambda = ](Args ...) - >返回{/*...*/};
//大致相当于:
struct {
返回operator()(Args ...){/*...*/}
}
lambda; //匿名结构的实例
就像任何其他类一样,当你传递它的实例时
按值复制的对象将被复制到
struct
:
auto lambda = [=](Args ...) - >返回{/ * ... use v,captured by value ... * /};
//大致相当于:
struct Temporary {//注意:我们不能使它成为一个匿名结构,因为我们需要
//一个构造函数,但是只是一个语法quirk
const Value v; //注意:除非lambda是可变的,否则默认值为const。
临时(值v_):v(v_){}
返回operator()(Args ...){/ *。 ..使用v,捕获的值... * /}
}
lambda(v); // struct的实例
同样,传递它只意味着你传递数据
同样,通过引用捕获的对象被引用到
struct
:
auto lambda = [&](Args ...) - >返回{/ * ...使用v,通过引用捕获... * /};
//大致相当于:
struct Temporary {
Value& v; //注意:捕获通过引用是非常量
临时(值& v_):v(v_){}
返回operator()(Args ...){/ * ... use v ,通过引用捕获... * /}
}
lambda(v); // struct的实例
这是几乎所有当谈到lambdas本身
std: :function
std :: function
是一个通用的包装函数, (lambdas,独立/静态/成员函数,函子类,像我所展示的,...)。
std ::函数
非常复杂,因为它们必须支持所有这些情况。根据函数的确切类型,这需要至少以下数据(给出或者获取实现细节):
- 指向独立/静态函数。
或
- 指向函数的副本 [见下面的注释] 的指针(动态分配以允许任何类型的函子,正如你所指出的)。
- 一个指向分配器的指针,它能够复制函子和函数本身(因为可以使用任何类型的函子,指针函数应该是
void *
,因此必须有这样的机制 - 可能使用多态性aka基类+虚方法,派生类在本地生成template
构造函数)。
事先知道它必须存储哪种函数(并且这可以通过
我不知道 标准规定了它,但是在运行时会做出决定。 这肯定是一个新的副本,底层函数不共享: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()>
四个64位指针)和可选的函数的动态分配副本(正如我已经说过,只包含捕获的对象,你不复制代码)。
回答问题
是将lambda传递给这样的函数的代价? [问题的上下文:按值]
< blockquote>
好吧,你可以看到它主要取决于你的函数(或者是一个手工的
struct
函数或lambda)与直接传递一个结构
函数的值相比,开销是可以忽略不计的,但它当然比传递astruct
函数作为引用。
通过
const&
传递的函数对象不能创建副本?
$ b b恐怕这很难用通用的方式回答。有时你需要通过
const
引用,有时通过值,有时通过 rvalue 引用,以便可以移动它。它真的取决于你的代码的语义。
你应该选择哪一个规则是一个完全不同的主题IMO,只是记住,他们是相同的任何其他对象。
无论如何,你现在有所有的键做出明智的决定(再次,取决于你的代码和语义)。
Passing a lambda is really easy in c++11:
func( []( int arg ) { // code } ) ;
But I'm wondering, what is the cost of passing a lambda to a function like this? What if func passes the lambda to other functions?
void func( function< void (int arg) > f ) { doSomethingElse( f ) ; }
Is the passing of the lambda expensive? Since a
function
object can be assigned 0,function< void (int arg) > f = 0 ; // 0 means "not init"
it leads me to think that function objects kind of act like pointers. But without use of
new
, then it means they might be like value-typedstruct
or classes, which defaults to stack allocation and member-wise copy.How is a C++11 "code body" and group of captured variables passed when you pass a function object "by value"? Is there a lot of excess copy of the code body? Should I have to mark each
function
object passed withconst&
so that a copy is not made:void func( const function< void (int arg) >& f ) { }
Or do function objects somehow pass differently than regular C++ structs?
解决方案Disclaimer: my answer is somewhat simplified compared to the reality (I put some details aside) but the big picture is here. Also, the Standard does not fully specify how lambdas or
std::function
must be implemented internally (the implementation has some freedom) so, like any discussion on implementation details, your compiler may or may not do it exactly this way.But again, this is a subject quite similar to VTables: the Standard doesn't mandate much but any sensible compiler is still quite likely to do it this way, so I believe it is worth digging into it a little. :)
Lambdas
The most straightforward way to implement a lambda is kind of an anonymous
struct
:auto lambda = [](Args...) -> Return { /*...*/ }; // roughly equivalent to: struct { Return operator ()(Args...) { /*...*/ } } lambda; // instance of the anonymous struct
Just like any other class, when you pass its instances around you never have to copy the code, just the actual data (here, none at all).
Objects captured by value are copied into the
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
Again, passing it around only means that you pass the data (
v
) not the code itself.
Likewise, objects captured by reference are referenced into the
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
That's pretty much all when it comes to lambdas themselves (except the few implementation details I ommitted, but which are not relevant to understanding how it works).
std::function
std::function
is a generic wrapper around any kind of functor (lambdas, standalone/static/member functions, functor classes like the ones I showed, ...).The internals of
std::function
are pretty complicated because they must support all those cases. Depending on the exact type of functor this requires at least the following data (give or take implementation details):
- A pointer to a standalone/static function.
Or,
- A pointer to a copy[see note below] of the functor (dynamically allocated to allow any type of functor, as you rightly noted it).
- A pointer to the member function to be called.
- A pointer to an allocator that is able to both copy the functor and itself (since any type of functor can be used, the pointer-to-functor should be
void*
and thus there has to be such a mechanism -- probably using polymorphism aka. base class + virtual methods, the derived class being generated locally in thetemplate<class Functor> function(Functor)
constructors).Since it doesn't know beforehand which kind of functor it will have to store (and this is made obvious by the fact that
std::function
can be reassigned) then it has to cope with all possible cases and make the decision at runtime.Note: I don't know where the Standard mandates it but this is definitely a new copy, the underlying functor is not shared:
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
So, when you pass a
std::function
around it involves at least those four pointers (and indeed on GCC 4.7 64 bitssizeof(std::function<void()>
is 32 which is four 64 bits pointers) and optionally a dynamically allocated copy of the functor (which, as I already said, only contains the captured objects, you don't copy the code).
Answer to the question
what is the cost of passing a lambda to a function like this?[context of the question: by value]
Well, as you can see it depends mainly on your functor (either a hand-made
struct
functor or a lambda) and the variables it contains. The overhead compared to directly passing astruct
functor by value is quite negligible, but it is of course much higher than passing astruct
functor by reference.Should I have to mark each function object passed with
const&
so that a copy is not made?I'm afraid this is very hard to answer in a generic way. Sometimes you'll want to pass by
const
reference, sometimes by value, sometimes by rvalue reference so that you can move it. It really depends on the semantics of your code.The rules concerning which one you should choose are a totally different topic IMO, just remember that they are the same as for any other object.
Anyway, you now have all the keys to make an informed decision (again, depending on your code and its semantics).
这篇关于如何表示和传递C ++ 11 lambdas?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!
查看全文