Bind 与 Lambda?
我有一个关于首选哪种风格的问题:std::bind Vs lambda in C++0x。我知道它们以某种方式服务于不同的目的,但让我们举一个交叉功能的例子。
使用lambda
:
uniform_int<> distribution(1, 6);
mt19937 engine;
// lambda style
auto dice = [&]() { return distribution(engine); };
使用bind
:
uniform_int<> distribution(1, 6);
mt19937 engine;
// bind style
auto dice = bind(distribution, engine);
我们应该选择哪一个?为什么?与上述示例相比,假设情况更复杂。即一种相对于另一种的优点/缺点是什么?
I have a question about which style is preferred: std::bind Vs lambda in C++0x. I know that they serve -somehow- different purposes but lets take an example of intersecting functionality.
Using lambda
:
uniform_int<> distribution(1, 6);
mt19937 engine;
// lambda style
auto dice = [&]() { return distribution(engine); };
Using bind
:
uniform_int<> distribution(1, 6);
mt19937 engine;
// bind style
auto dice = bind(distribution, engine);
Which one should we prefer? why? assuming more complex situations compared to the mentioned example. i.e. What are the advantages/disadvantages of one over the other?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(8)
C++0x lambda 是单态的,而 bind 可以是多态的。你不能让
a 和 b 之类的东西必须有已知的类型。另一方面,tr1/boost/phoenix/lambda 绑定允许您执行此操作:
请注意,类型 A 和 B 在此未固定。只有当f被实际使用时才会推导出这两个。
C++0x lambdas are monomorphic, while bind can be polymorphic. You cannot have something like
a and b must have known types. On the other hand, tr1/boost/phoenix/lambda bind allows you to do this:
Note that the types A and B are not fixed here. Only when f is actually used these two will be deduced.
正如您所说,bind 和 lambda 并不完全针对同一目标。
例如,对于使用和编写 STL 算法,lambda 显然是赢家,恕我直言。
为了说明这一点,我记得一个非常有趣的答案,在堆栈溢出上,有人询问十六进制幻数的想法(例如 0xDEADBEEF、0xCAFEBABE、0xDEADDEAD 等),并被告知如果他是真正的 C++ 程序员,他只会有下载英语单词列表并使用简单的 C++ 一行代码:)
这段代码,在纯 C++98 中,打开英语单词文件,扫描每个单词并仅打印长度为 8 的带有 'a'、'b 的单词'、'c'、'd'、'e' 或 'f' 字母。
现在,打开 C++0X 和 lambda :
这读起来仍然有点繁重(主要是因为 istream_iterator 业务),但比绑定版本简单得多:)
As you said, bind and lambdas don't quite exactly aim at the same goal.
For instance, for using and composing STL algorithms, lambdas are clear winners, IMHO.
To illustrate, I remember a really funny answer, here on stack overflow, where someone asked for ideas of hex magic numbers, (like 0xDEADBEEF, 0xCAFEBABE, 0xDEADDEAD etc.) and was told that if he were a real C++ programmer he would simply have download a list of English words and use a simple one-liner of C++ :)
This snippet, in pure C++98, open the English words file, scan each word and print only those of length 8 with 'a', 'b', 'c', 'd', 'e' or 'f' letters.
Now, turn on C++0X and lambda :
This is still a bit heavy to read (mainly because of the istream_iterator business), but a lot simpler than the bind version :)
C++ 0x lamdba 语法比绑定语法更具可读性。一旦进入超过 2-3 级绑定,您的代码就会变得几乎不可读且难以维护。我更喜欢更直观的 lambda 语法。
The C++ 0x lamdba syntax is more readable than the bind syntax. Once you get into more than 2-3 level bind, you code becomes pretty much unreadable and difficult to maintain. I would prefer the more intuitive lambda syntax.
lambda 的一个关键优点是它们可以静态引用成员函数,而 bind 只能通过指针引用它们。更糟糕的是,至少在遵循“itanium c++ ABI”(例如 g++ 和 clang++)的编译器中,指向成员函数的指针是普通指针大小的两倍。
因此,至少对于 g++,如果你执行类似 std::bind(&Thing::function, this) 的操作,你会得到一个大小为三个指针的结果,两个指针指向成员函数一个用于 this 指针。另一方面,如果您执行
[this](){function()}
,您将得到一个大小只有一个指针的结果。std::function 的 g++ 实现最多可以存储两个指针,无需动态内存分配。因此,将成员函数绑定到 this 并将其存储在 std::function 中将导致动态内存分配,而使用 lambda 并捕获 this 则不会。
来自评论:
否
“指向成员函数的指针”(至少在“itanium C++ ABI”下,但我怀疑其他编译器是类似的)大小是两个指针,因为它存储了指向实际成员函数的指针(或 vtable 偏移量)虚拟成员函数)以及“this 指针调整”以支持多重继承。将 this 指针绑定到成员成员函数会产生一个大小为三个指针的对象。
另一方面,对于 lambda,每个 lambda 都有一个唯一的类型,并且有关要运行的代码的信息存储为类型的一部分,而不是值的一部分。因此,只有捕获需要存储为 lambda 值的一部分。至少在 g++ 下,按值捕获单个指针的 lambda 具有单个指针的大小。
lambda、指向成员函数的指针或绑定结果都不将参数数量存储为数据的一部分。该信息作为其类型的一部分存储。
std::function 的 g++ 实现有四个指针大小,它由一个指向“调用者”函数的函数指针、一个指向“管理器”函数的函数指针和一个大小为两个指针的数据区域组成。当程序想要调用存储在 std::function 中的可调用对象时,将使用“invoker”函数。当 std::function 中的可调用对象需要复制、销毁等时,将调用管理器函数
。当您构造或分配给 std::function 时,将通过模板生成调用程序和管理器函数的实现。这就是允许 std::function 存储任意类型的原因。
如果您分配的类型能够适合 std::function 的数据区域,那么 g++ 的实现(我强烈怀疑大多数其他实现)会将其直接存储在那里,因此不需要动态内存分配。
为了演示为什么在这种情况下 lambda 比绑定好得多,我编写了一些小测试代码。
我使用带有 -O2 和 -fno-rtti 的“armv7-a clang trunk”选项将其输入 godbolt,并查看了生成的汇编程序。我已经手动分离出了 bar 和 baz 的汇编器。我们首先看一下 bar 的汇编器。
我们看到,bar 本身非常简单,它只是用 this 指针的值以及指向调用者和管理器函数的指针填充 std::function 对象。 “invoker”和“manager”函数也非常简单,看不到动态内存分配,并且编译器已将 foo 内联到“invoker”函数中。
现在让我们看看 baz 的汇编程序:
我们发现它几乎在每个方面都比 bar 的代码更糟糕。 baz 本身的代码现在长度增加了一倍以上,并且包含动态内存分配。
调用者函数不能再内联 foo 甚至直接调用它,而是必须经历调用成员函数指针的整个繁琐过程。
管理器功能也更加复杂,并且涉及动态内存分配。
A key advantage of lambdas is they can reference member functions statically, while bind can only reference them through a pointer. Worse, at least in compilers that follow the "itanium c++ ABI" (e.g. g++ and clang++) a pointer to a member function is twice the size of a normal pointer.
So with g++ at least, if you do something like
std::bind(&Thing::function, this)
you get a result that is three pointers in size, two for the pointer to member function and one for the this pointer. On the other hand if you do[this](){function()}
you get a result that is only one pointer in size.The g++ implementation of std::function can store up to two pointers without dynamic memory allocation. So binding a member function to this and storing it in a std::function will result in dynamic memory allocation while using a lambda and capturing this will not.
From a comment:
No
A "pointer to member function" is (at least under the "itanium C++ ABI", but I suspect other compilers are similar) two pointers in size, because it stores both a pointer to the actual member function (or a vtable offset for virtual member functions) and also a "this pointer adjustment" to support multiple inheritance. Binding the this pointer to the member member function results in an object three pointers in size.
With a lambda on the other hand, every lambda has a unique type, and the information on what code to run is stored as part of the type, not part of the value. Therefore only the captures need to be stored as part of the lambda's value. At least under g++ a lambda that captures a single pointer by value has the size of a single pointer.
Neither the lambda, the pointer to member function or the result of bind store the number of parameters as part of their data. That information is stored as part of their type.
The g++ implementation of a std::function is four pointers in size, it consists of a function pointer to a "caller" function, a function pointer to a "manager" function and a data area that is two pointers in size. The "invoker" function is used when a program wants to call the callable object stored in the std::function. The manager function is called when the callable object in the std::function needs to be copied, destroyed etc.
When you construct or assign to a std::function, implementations of the invoker and manager function are generated through templating. This is what allows the std::function to store arbitrary types.
If the type you assign is able to fit in the std::function's data area then g++'s implementation (and I strongly suspect most other implementations) will store it directly in there, so dynamic memory allocation is not needed.
To demonstrate why a lambda is far better than bind in this case I wrote some small test code.
I fed this into godbolt using the "armv7-a clang trunk" option with -O2 and -fno-rtti and looked at the resulting assembler. I have manually separated out the assembler for bar and baz. Lets first look at the assembler for bar.
We see, that bar itself is very simple, it's just filling out the std::function object with the value of the this pointer and with pointers to the caller and manager functions. The "invoker" and "manager" functions are also pretty simple, there is no dynamic memory allocation in sight and the compiler has inlined foo into the "invoker" function.
Now lets look at the assembler for baz:
We see it's worse than the code for bar in almost every respect. The code for baz itself is now over twice as long and includes dynamic memory allocation.
The invoker function can no longer inline foo or even call it directly, instead it must go through the whole rigmarole of calling a pointer to member function.
The manager function is also substantially more complex and involves dynamic memory allocation.
lambda 的好处之一是,当您需要在现有函数之上添加一点大逻辑时,它们会更有用。
使用绑定,您被迫创建一个新的函数/方法/函子,即使逻辑只在这一处需要。您需要想出一个合适的名称,它可能会使代码更难以理解,因为它可能会让您拆分相关逻辑。
使用 lambda,您可以在 lambda 内添加新逻辑(但如果创建新的可调用对象有意义,则不必这样做)。
One of the benefits of lambdas is they are way more useful when you need to add a little big of logic on top of an existing function.
With bind, you are forced to create a new function/method/functor even if the logic is only ever needed in this one place. You need to come up with an appropriate name and it can make the code less understandable as it potentially makes you split up related logic.
With lambda, you can add the new logic inside the lambda (but are not forced to if it makes sense to create a new callable).
我认为这更多是一个品味问题。快速掌握新技术或熟悉函数式编程的人可能会更喜欢 lambda 语法,而更保守的程序员肯定会更喜欢 bind,因为它更接近传统的 C++ 语法。
这样的决定应该与将要使用该代码的人员协调做出,可能是通过多数投票来做出的。
但这并没有改变事实,lambda 语法更加强大和简洁。
I think it's more a matter of taste. People that quickly grasp new technologies, or are familiar with functional programming will probably prefer lambda syntax, while more conservative programmers will definitively prefer bind, as it is more in par with the traditional C++ syntax.
Such a decision should be made in coordination with the people that will be working with the code, probably through a majority vote.
Which doesn't change the fact however, that lambda syntax is much more powerful and cleaner.
C++0x lambda 本质上取代了绑定。没有什么是你可以绑定的,你不能重新创建一个简单的包装器 lambda 来实现相同的目的。一旦 lambda 支持广泛传播,std::tr1::bind 就会走 std::bind1st 等的路。这很好,因为由于某种原因,大多数程序员很难理解绑定。
C++0x lambdas essentially replace bind. There is nothing you can bind that you can't recreate a trivial wrapper lambda to achieve the same. std::tr1::bind will go the way of std::bind1st, etc once lambda support is wide spread. Which is good, because for some reason most programmers have a hard time getting their head around bind.
从 C++20 开始(自 2009 年提出这个问题以来发布!),有第三种选择: “nofollow noreferrer”>
std::bind_front
。 (C++23 也有std::bind_back
。)这样使用:它也可以用于成员函数:
它不会遇到
std:bind 的一些问题
:它有一个更简单的实现,因为它不允许重新组织参数的顺序,因此它更有可能被编译器优化,并且它具有移动感知能力,因此可以正确转发其参数。然而,它仍然可能比 lambda 生成更多的代码,因为函数指针/成员函数指针存储为结果值的数据成员而不是其类型的一部分。我不认为这会对性能产生很大的影响,除非你处于绝对最紧密的循环中。
As of C++20 (released since this question was asked in 2009!) there is a third choice:
std::bind_front
. (C++23 also hasstd::bind_back
.) Use like this:It can also be used for member functions:
It doesn't suffer from some of the problems of
std:bind
: it has a simpler implementation because it doesn't allow reorganising the order of parameters, so it's more likely to be optimised by the compiler, and it's move-aware so forwards its arguments properly.However, it does still potentially generate a little more code than a lambda, because the function pointer / member function pointer is stored as a data member of the resulting value rather than part of its type. I wouldn't expect this to make a big difference to performance unless you're in the absolute tightest of loops though.