【问题标题】:How std::bind works with member functionsstd::bind 如何与成员函数一起使用
【发布时间】:2016-10-04 19:38:30
【问题描述】:

我正在使用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 是否有某种实现需要引用?

【问题讨论】:

  • &amp; 后跟标识符并不意味着“引用”。除非它声明了一个变量,否则&amp; 将在前面加上一个类型名。当&amp; 应用于标识符时,您将获得一个指针
  • 一般情况下,避免使用std::bind。它有一些非常深刻的怪癖,即使在你掌握了基础知识之后,它也会产生令人惊讶的结果。我觉得不值得。

标签: c++ c++11 std stdbind


【解决方案1】:

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

在回答这个问题之前,我们先回过头来看看你第一次使用std::bind()是什么时候使用

std::bind(my_divide, 2, 2)

你提供了一个功能。当一个函数被传递到任何地方时,它会衰减为一个指针。上面的表达式等价于这个,显式取地址

std::bind(&my_divide, 2, 2)

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

由于成员函数很常见,std::bind() 提供了处理指向成员函数的指针的支持。当你使用&amp;print_sum 时,你只会得到一个指向成员函数的指针,即void (Foo::*)(int, int) 类型的实体。虽然函数名称隐含地衰减为指向函数的指针,即 &amp; 可以省略,但对于成员函数(或数据成员,就此而言)而言,情况并非如此:要获得指向成员函数的指针,必须使用&amp;

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

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

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

std::bind(&Foo::print_sum, &foo, 95, _1)

生成的可调用对象使用&amp;foo,即指向foo(类型为Foo*)的指针作为对象。 std::bind() 足够聪明,可以使用任何看起来像指针的东西,任何可转换为适当类型的引用的东西(如std::reference_wrapper&lt;Foo&gt;),或者当第一个参数是指向指针时,对象的 [copy] 作为对象会员。

我怀疑,您从未见过指向成员的指针 - 否则会很清楚。这是一个简单的例子:

#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 对象的指针和一个指向成员函数的指针。它调用每个对象指向的成员函数。这个有趣的-&gt;* 运算符将指向成员的指针应用到指向对象的指针。还有一个 .* 运算符,它将指向成员的指针应用于对象(或者,因为它们的行为就像对象一样,所以引用对象)。由于指向成员函数的指针需要一个对象,因此有必要使用此运算符来请求一个对象。在内部,std::bind() 安排同样的事情发生。

apply() 使用两个指针和&amp;Foo::f 调用时,它的行为与在各自对象上调用成员f() 完全相同。同样,当使用两个指针调用apply()&amp;Foo::g 时,它的行为与在各自对象上调用成员g() 完全相同(语义行为相同,但编译器可能会更难)时间内联函数,并且在涉及到成员的指针时通常会失败)。

【讨论】:

  • 读到一半,我很纳闷,谁能写出这么棒的解释?我猜到了!
  • 惊人的答案,Dietmar。对我来说,关键是:(1) 虽然函数名称隐含地衰减为指向函数的指针,但对于成员函数来说并非如此:要获得指向成员函数的指针,必须使用&., (2) std::bind() 检测到传递了一个指向成员函数的指针,并且很可能将其转换为可调用对象,并且 (3)要使用指向成员函数的指针,需要一个对象。当通过 std::bind() 使用指向成员的指针时,std::bind() 的第二个参数相应地需要指定对象来自的时间。非常感谢!
【解决方案2】:

来自std::bind docs

bind( F&amp;&amp; f, Args&amp;&amp;... 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::invokein general) 以统一的方式涵盖所有这些情况。如果f 是指向Foo 成员的指针,那么提供给绑定的第一个Arg 预计将是Foo 的一个实例(bind(&amp;Foo::print_sum, foo, ...) 也可以工作,但foo 被复制)或一个指向Foo指针,就像你的例子一样。

这里有更多关于pointers to members12 的阅读资料,提供了有关 bind 期望什么以及它如何调用存储函数的完整信息。

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

auto f = [&](int n) { return foo.print_sum(95, n); }

【讨论】:

  • 奇怪的是,&amp;Foo::print_sum 不是可调用的!指向成员的指针,甚至指向成员函数的指针,not 是否有函数调用运算符! std::mem_fn(&amp;Foo::print_sum) 将是可调用的。但是,std::bind() 确实理解以指向成员的指针作为其第一个参数来调用。
  • @DietmarKühl Callable 在 c++17 中的含义有点不同:en.cppreference.com/w/cpp/types/is_callablestd::mem_fnstd::bindstd::invoke 的整个家族只是在基本的 Callable 类型上提供了一个很好的接口。
  • 但这只是一个关于术语的问题。
  • 谢谢@DmitryPanteleev!我也在阅读与函数指针和指向成员函数的指针相关的非常好的答案here
猜你喜欢
  • 2014-01-09
  • 2012-10-28
  • 2014-09-12
  • 1970-01-01
  • 1970-01-01
  • 2013-02-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多