std::bind和std::function问题

11
int func(int x){return x;}
...
std::function<int(int)> x = std::bind(func, std::placeholders::_1);
x(123);
  1. x(123)会调用std::function生成的函数对象的operator()方法,该方法进而调用std::bind生成的函数对象的operator()方法,最终调用func函数。编译器可以将其优化为直接调用func(123)
  2. std::bind生成的函数对象存在于何处?在哪个作用域中?std::bind如何命名它?(是否可能出现名称冲突)
  3. lambda表达式是否可以替代所有使用std::bind的场景?
  4. 与使用lambda表达式实现相比,std::bind是否同样高效?
  5. std::function模板参数的语法是什么意思?这如何解析,并且我如何在其他地方使用该模板参数语法?

2
对于第二点,您可能会对http://www.artima.com/cppsource/type_erasure.html感兴趣。 - Flexo
4
请逐个提问。每个SO帖子都是一个问题。 - Lightness Races in Orbit
这更像是一组问题而不是一个具体的问题,它们涵盖了从语法(function<> 的参数)到行为(bind 的结果在哪里?能否用 lambda 替代?)再到性能(是否会被优化?)的范围。 - David Rodríguez - dribeas
很遗憾,就我所记得的(看到由VS2012 - release生成的代码),它与x(123)调用根本没有可比性。例如,它不会内联。如果您使用lambda,则可以进行内联。 - Ghita
3个回答

16

调用 x(123) 是否实际上调用 std::function 生成的函数对象的 operator(),它反过来调用 std::bind 生成的函数对象的 operator(),最终调用 func?这是否会被优化为像调用 func(123) 一样优化?

我不会将 std::function 的 operator() 描述为“生成的”(它是一个普通成员),但除此之外,这是一个很好的描述。优化取决于您的编译器,但请注意,要优化 std::function 的间接性(需要使用类型抹消),编译器可能需要执行非常复杂的操作。

std::bind 生成的函数对象在哪里存在?在什么范围内?std::bind 如何命名它?(是否可能存在名称冲突)

对 std::bind 的调用返回一个未指定类型的函数对象,该函数对象的副本存储在 x 对象内部。此副本将与 x 本身一样长时间存在。没有涉及名称,因此我不确定您的意思。

能否用 lambda 替换 std::bind 的所有用途?

不行。考虑 auto bound = std::bind(functor, _1);,其中 functor 是具有重载 operator() 的类型,比如在 long 和 int 上。那么,bound(0L) 的效果与 bound(0) 不同,而您无法使用 lambda 复制这种情况。

std::bind 是否和使用 lambda 实现一样优秀?

这取决于编译器。请自行测试。

std::function 模板参数的语法是什么?它如何解析?我如何在其他地方使用该模板参数语法?

它是一个函数类型。也许您已经熟悉指向函数的指针/引用的语法:void(*)()int(&)(double)。然后,只需将指针/引用从类型中移除,就可以得到函数类型:void()int(double)。您可以这样使用:

typedef int* function_type(long);
function_type* p; // pointer to function

2
+1 是问题#2唯一正确的答案:一个函数对象只是一个普通对象,不存在神奇的类生成。 - Luc Touraille
1
第一次就掌握了静态多态性(bind)与单态原型(function<>)之间的区别 :) 这确实是一个深刻但容易被忽视的区别。(从一开始你就有我的+1) - sehe
+1 是使用 bind 的用例,它无法通过 lambda 复制。 - David Rodríguez - dribeas

6

1 . x(123) 实际上调用了由 std::function 生成的函数对象的 operator(),该函数对象进而调用了由 std::bind 生成的函数对象的 operator(),最终调用了 func。这个过程是否会被优化成和直接调用 func(123) 一样优秀的结果?

如果启用了优化,则 'stuff' 将被内联,您可以确保这与直接调用 func(123) 一样优秀。

2 . std::bind 生成的函数对象存在于哪个作用域中?它的名称是如何命名的?(是否可能存在名称冲突)

具体而言,bind 生成一个瞬时的、实现定义的 bind 表达式,可分配给 function<>。Function 只是一个类模板(感谢 Luc T.)。它位于标准库中。但是,bind 表达式是实现定义的。

标准库确实带有特性(std::is_bind_expression<>),允许MPL检测此类表达式。与std::function相比,bind表达式的一个决定性特征是它们是所谓的“延迟可调用对象”(即它们保留完整的调用站点语义,包括在实际应用站点上选择重载的能力)。另一方面,std::function<>则承诺单个原型,并通过类型擦除(类似于variantany)内部存储可调用对象

3 .lambda表达式能否替代std::bind的所有用途?

4 .将std::bind实现为lambda表达式是否和std::bind一样优化?

AFAICT,lambda表达式应该编译成与绑定表达式大致相同的代码。我认为lambda表达式无法完成绑定表达式可以完成的嵌套绑定表达式的一件事。 编辑 虽然无法使用lambda表达式复制嵌套绑定表达式的特定习惯用法,但是lambda表达式当然能够更自然地表达(几乎)相同的内容:
 bind(f, bind(g, _1))(x); 
 // vs.
 [](int x) { f(g(x)); };

 

5. std::function 模板参数的语法是什么?它是如何解析的,我该如何在其他地方使用该模板参数语法?
这只是一个函数签名(函数的类型),作为模板参数传递。
您也可以将其用作函数参数类型,它会退化为函数指针(类似于按值传递数组参数如何退化为指针,感谢 David!)。 实际上,在大多数情况下,只要您不需要命名变量/类型,就可以在任何地方使用它。
 void receiveFunction(void(int, double)); // spunky 'function<>'-style syntax

 void sample(int, double) { } 

 int main()
 { 
     receiveFunction(sample);
 }

 void receiveFunction(void (*f)(int, double)) // boring 'old' style syntax
 //                   void ( f)(int, double)  // ... also ok
 {
     // ..
 }

1
我不同意第二点:正确的答案是bind没有生成任何函子,只是一个由函数返回的临时对象,它没有名称并且将在出现的表达式的生命周期内存在(就像所有临时对象一样)。这个临时对象用于初始化x - Luc Touraille
唯一未指定的是返回对象的类型,我们只知道它必须满足一组要求:它必须是可移动构造的,可以使用给定的一组参数调用等。 - Luc Touraille
+1,但这只是一个函数签名,被解析为函数指针的类型...... "指针"部分是错误的,类型是函数的签名,并且它不会被解析为指针,而是作为一个签名(从中提取参数以生成 operator() 的签名,整个过程中没有涉及指针)。至于您后面的例子,问题在于函数声明中的类型“function ...”以与“array of”的方式转换为“指向函数的指针”,这样就变成了指向第一个元素的指针,但是... - David Rodríguez - dribeas
1
关于lambda表达式不能模拟嵌套绑定表达式的说法是不正确的,实际上这可以非常轻松地实现。对于您的特定示例:[]( int x ) { f(g(x)); },实际上更易读... - David Rodríguez - dribeas
1
关于第一点,"如果您启用了优化,则'stuff'将被内联,并且您可以指望这与调用func(123)一样优化。" 我完全不同意——std :: function <>在内部使用类型擦除,而无法通过类型擦除进行内联。也就是说,直接调用绑定表达式可以轻松内联,但调用std :: function <>永远不能内联原始函数/函数对象。关于嵌套的绑定表达式,我认为该行为特定于boost :: bind,并不适用于std :: bind,但我不确定。 - ildjarn
显示剩余5条评论

0
能否用lambda表达式替代std::bind的所有用法?
C++14允许lambda表达式在很大程度上替代bind。特别是针对Luc Danton的回答,C++14中,您可以编写使用auto的模板化lambda表达式,使得bound(0)bound(0L)有不同的行为。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接