std :: bind如何与成员函数一起使用

Tar*_*rod 21 c++ std stdbind c++11

我正在使用std::bind但是当我们将它与成员类函数一起使用时,我仍然不知道它是如何工作的.

如果我们有以下功能:

double my_divide (double x, double y) {return x/y;}
Run Code Online (Sandbox Code Playgroud)

我完全理解下一行代码:

auto fn_half = std::bind (my_divide,_1,2);               // returns x/2

std::cout << fn_half(10) << '\n';                        // 5
Run Code Online (Sandbox Code Playgroud)

但是现在,通过以下代码我们有一个绑定到成员函数我有一些问题.

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);
Run Code Online (Sandbox Code Playgroud)
  • 为什么第一个参数是参考?我想得到一个理论上的解释.

  • 第二个参数是对象的引用,对我来说是最复杂的部分需要理解.我认为这是因为std::bind需要一个背景,我是对的吗?总是这样吗?std::bind当第一个参数是成员函数时,是否有某种实现要求引用?

Die*_*ühl 39

当你说"第一个参数是一个引用"时,你肯定会说"第一个参数是一个指针 ":&运算符获取一个对象的地址,产生一个指针.

在回答这个问题之前,让我们回过头来看看你第一次使用它std::bind()时的用法

std::bind(my_divide, 2, 2)
Run Code Online (Sandbox Code Playgroud)

你提供一个功能.当函数在任何地方传递时它会衰减成指针.上面的表达式相当于这一个,明确地取了地址

std::bind(&my_divide, 2, 2)
Run Code Online (Sandbox Code Playgroud)

第一个参数std::bind()是一个标识如何调用函数的对象.在上面的例子中,它是一个指向函数类型的指针double(*)(double, double).任何其他具有合适的函数调用操作符的可调用对象也可以.

由于成员函数非常常见,因此std::bind()支持处理指向成员函数的指针.当你使用时,&print_sum你只需要一个指向成员函数的指针,即一个类型的实体void (Foo::*)(int, int).虽然函数名称隐式地衰减到指向函数的指针,即&可以省略,但对于成员函数(或数据成员,对于那个问题)则不一样:要获得指向成员函数的指针,必须使用&.

请注意,指向成员的指针特定于a,class但它可以与该类的任何对象一起使用.也就是说,它独立于任何特定对象.C++没有直接的方法来获得直接绑定到对象的成员函数(我认为在C#中,您可以通过使用具有应用成员名称的对象来获取直接绑定到对象的函数;但是,它已经超过10年了我上次编写了一些C#).

在内部,std::bind()检测到传递成员函数的指针并且很可能将其转换为可调用对象,例如,通过使用std::mem_fn()其第一个参数.由于非static成员函数需要一个对象,因此解析可调用对象的第一个参数是引用或指向相应类对象的[智能]指针.

要使用指向成员函数的指针,需要一个对象.当使用带有std::bind()第二个参数的成员的指针时,std::bind()相应地需要指定对象何时来自.在你的例子中

std::bind(&Foo::print_sum, &foo, 95, _1)
Run Code Online (Sandbox Code Playgroud)

生成的可调用对象使用&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);
}
Run Code Online (Sandbox Code Playgroud)

该函数apply()只获取两个指向Foo对象的指针和一个指向成员函数的指针.它调用每个对象指向的成员函数.这个有趣的->*运算符将指向成员的指针应用于指向对象的指针.还有一个.*操作符将一个指向成员的指针应用于一个对象(或者,因为它们就像对象一样,对一个对象的引用).由于指向成员函数的指针需要一个对象,因此必须使用这个请求对象的运算符.在内部,std::bind()安排相同的事情发生.

apply()使用两个指针调用&Foo::f它时,它的行为与f()在相应对象上调用成员的行为完全相同.同样,当apply()使用两个指针进行调用时,&Foo::g它的行为与g()在相应对象上调用成员的行为完全相同(语义行为相同,但编译器可能会更难以内联函数,并且通常在执行此操作时失败涉及成员的指针).

  • 读到一半,我很纳闷,谁能写出这么棒的解释?我早就猜到了! (4认同)
  • 惊人的答案,迪特玛。对我来说,关键是: **(1)** 虽然函数名称隐式衰减为指向函数的指针,但对于成员函数而言并非如此:要获得指向成员函数的指针,必须使用 &amp;., **(2)** std::bind() 检测到一个指向成员函数的指针被传递并且很可能将它变成一个可调用对象,并且 **(3)** 使用指向成员函数的指针是一个对象需要。当 std::bind() 使用指向成员的指针时, std::bind() 的第二个参数相应地需要指定对象来自何时。非常感谢! (2认同)

Dmi*_*eev 6

来自std :: bind docs:

bind( F&& f, Args&&... args );其中f是a 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)
Run Code Online (Sandbox Code Playgroud)

std::bind(并且std::invoke 一般而言)以统一的方式涵盖所有这些案例.如果f是的指针到构件Foo,则第一Arg提供结合预计的一个实例Foo(bind(&Foo::print_sum, foo, ...)也可以,但是foo被复制)或指针Foo,像例如在你有.

下面是关于成员指针的更多内容,12提供了有关bind期望的内容以及它如何调用存储函数的完整信息.

您也可以使用lambdas std::bind,这可能更清楚:

auto f = [&](int n) { return foo.print_sum(95, n); }
Run Code Online (Sandbox Code Playgroud)