我正在与std::bind
合作,但当我们将它与成员类函数一起使用时,我仍然无法了解它是如何工作的。
如果我们有以下功能:
double my_divide (double x, double y) {return x/y;}
我完全理解下一行代码:
auto fn_half = std::bind (my_divide,_1,2); // returns x/2
std::cout << fn_half(10) << '\n'; // 5
但是现在,使用以下代码我们有一个绑定到成员函数我有一些问题。
struct Foo {
void print_sum(int n1, int n2)
{
std::cout << n1+n2 << '\n';
}
int data = 10;
};
Foo foo;
auto f = std::bind(&Foo::print_sum, &foo, 95, _1);
f(5);
为什么第一个参数是参考?我想得到一个理论上的解释。
第二个参数是对象的引用,它对我来说是最难理解的部分。我认为这是因为std::bind
需要一个背景,我是对的吗?总是这样吗?当第一个参数是成员函数时,std::bind
某种实现需要引用吗?
答案 0 :(得分:31)
当你说“第一个参数是引用”时,你肯定会说“第一个参数是指针”:&
运算符获取一个对象的地址,屈服指针。
在回答这个问题之前,让我们回过头来看一下你使用时std::bind()
的第一次使用
std::bind(my_divide, 2, 2)
你提供了一个功能。当函数在任何地方传递时它会衰减成指针。上面的表达式与此明确相同,明确地采用地址
std::bind(&my_divide, 2, 2)
std::bind()
的第一个参数是一个识别如何调用函数的对象。在上面的例子中,它是一个指向类型为double(*)(double, double)
的函数的指针。任何其他具有合适的函数调用操作符的可调用对象也可以。
由于成员函数很常见,std::bind()
为处理指向成员函数的指针提供了支持。当您使用&print_sum
时,您只需获得指向成员函数的指针,即类型为void (Foo::*)(int, int)
的实体。虽然函数名隐式地衰减到指向函数的指针,即&
可以省略,但对于成员函数(或数据成员,对于那个问题)则不一样:获取指向成员函数的指针它是必须使用&
。
请注意,指向成员的指针特定于class
,但它可以与该类的任何对象一起使用。也就是说,它独立于任何特定对象。 C ++没有直接的方法来获得直接绑定到对象的成员函数(我认为在C#中,您可以通过使用具有应用成员名称的对象来获取直接绑定到对象的函数;但是,它已经超过10年了我最后编写了一些C#)。
在内部,std::bind()
检测到传递成员函数的指针,并且很可能将其转换为可调用对象,例如,通过使用std::mem_fn()
及其第一个参数。由于非static
成员函数需要一个对象,因此解析可调用对象的第一个参数是引用或指向相应类对象的[智能]指针。
要使用指向成员函数的指针,需要一个对象。当使用指向std::bind()
成员的指针时,std::bind()
的第二个参数相应地需要指定对象何时来自。在你的例子中
std::bind(&Foo::print_sum, &foo, 95, _1)
生成的可调用对象使用&foo
,即指向foo
(类型为Foo*
)的指针作为对象。 std::bind()
足够聪明,可以使用任何看起来像指针的东西,任何可转换为适当类型的引用的东西(如std::reference_wrapper<Foo>
),或者对象的[副本]作为第一个参数时的对象是指向成员的指针。
我怀疑,你从未见过指向会员的指针 - 否则会很清楚。这是一个简单的例子:
#include <iostream>
struct Foo {
int value;
void f() { std::cout << "f(" << this->value << ")\n"; }
void g() { std::cout << "g(" << this->value << ")\n"; }
};
void apply(Foo* foo1, Foo* foo2, void (Foo::*fun)()) {
(foo1->*fun)(); // call fun on the object foo1
(foo2->*fun)(); // call fun on the object foo2
}
int main() {
Foo foo1{1};
Foo foo2{2};
apply(&foo1, &foo2, &Foo::f);
apply(&foo1, &foo2, &Foo::g);
}
函数apply()
只需要两个指向Foo
个对象的指针和一个指向成员函数的指针。它调用每个对象指向的成员函数。这个有趣的->*
运算符正在将指向成员的指针应用于指向对象的指针。还有一个.*
运算符,它将指向成员的指针应用于对象(或者,就像它们的行为一样,对对象的引用)。由于指向成员函数的指针需要一个对象,因此必须使用这个请求对象的运算符。在内部,std::bind()
安排相同的事情发生。
当使用两个指针和apply()
调用&Foo::f
时,它的行为与在相应对象上调用成员f()
的行为完全相同。同样,当使用两个指针和apply()
调用&Foo::g
时,它的行为与在相应对象上调用成员g()
的行为完全相同(语义行为相同,但编译器是可能会更难以内联函数,并且在涉及成员的指针时通常会失败。
答案 1 :(得分:5)
bind( F&& f, Args&&... args );
其中f是Callable
,在您的情况下是指向成员函数的指针。与通常函数的指针相比,这种指针有一些特殊的语法:
typedef void (Foo::*FooMemberPtr)(int, int);
// obtain the pointer to a member function
FooMemberPtr a = &Foo::print_sum; //instead of just a = my_divide
// use it
(foo.*a)(1, 2) //instead of a(1, 2)
std::bind
(和std::invoke
in general)以统一的方式涵盖所有这些案例。如果f
是Foo
的成员指针,则提供给绑定的第一个Arg
应该是Foo
的实例(bind(&Foo::print_sum, foo, ...)
有效,但foo
被复制)或指针到Foo
,就像你的例子一样。
以下是关于pointers to members的更多内容,1和2提供了有关绑定所需内容以及调用存储函数的完整信息。
您也可以使用lambdas代替std::bind
,这可能更清楚:
auto f = [&](int n) { return foo.print_sum(95, n); }