对成员函数使用std :: function

时间:2015-05-08 18:45:58

标签: c++ c++11 c++-standard-library std-function member-functions

我的问题是关于将std :: function用于类方法。假设我有以下类层次结构:

class Foo {
public:
    virtual void print() {
        cout << "In Foo::print()" << endl;
    }

    virtual void print(int) {
        cout << "In Foo::print(int)" << endl;
    }
};

class Bar : public Foo {
public:
    virtual void print() override {
        cout << "In Bar::print()" << endl;
    }

    virtual void print(int) override {
        cout << "In Bar::print(int)" << endl;
    }
}

现在有另一个函数应该动态调用两个类方法中的一个取决于它的输入:

void call(Foo* foo, void (Foo::*func)(void)) {
    (foo->*func)();
}

Foo* foo = new Foo();
Bar* bar = new Bar();
call(foo, &Foo::print);
call(bar, &Foo::print);

当我使用g ++ / clang ++编译上面的代码片段时,它按预期工作,输出为:

In Foo::print()
In Bar::print()

我的问题是:

  1. 因为有两个具有相同名称的函数(重载):print,当我传递类函数的地址:&Foo::print时,编译器是如何知道我实际调用的是{{1}但不是Foo::print(void)

  2. 还有另一种方法可以概括上面的代码,以便Foo::print(int)的第二个参数可以同时使用void call(Foo*, xxx)Foo::print(void)

  3. 无论如何
  4. 使用C ++ 11 Foo::print(int)中的新功能来实现此功能?我理解为了将std::function与非静态类方法一起使用,我必须使用std::function将每个类方法与特定的类对象绑定,但这对我来说效率太低,因为我要绑定许多类对象。

2 个答案:

答案 0 :(得分:1)

首先,std::bind(差不多)完全过时了C ++ 11 lambdas。如果可以帮助它,请不要使用std :: bind。使用您的示例代码,其中一个比其他更清晰:

const auto lambda  = [=] { foo->print(); }; // Clear!
const auto binderv = std::bind( static_cast<void(Foo::*)()>( &Foo::print ), foo ); // Gets the void version
const auto binderi = std::bind( static_cast<void(Foo::*)(int)>( &Foo::print ), foo, std::placeholders::_1 ); // Gets the int version

//const auto binderv2 = std::bind( &Foo::print, foo ); // Error! Can't tell which Foo::print()
//const auto binderi2 = std::bind( &Foo::print, foo, std::placeholders::_1 ); // Error! Can't tell which Foo::print()

lambda();   // prints "void"
binderv();  // prints "void"
binderi(1); // prints "int"

其次,编译器如何知道调用哪个重载函数?与使用非成员函数时的方式相同:

#include <iostream>

void call( void (*fn)() )
{
    fn();
}

void print()    { std::cout << "void\n"; }
void print(int) { std::cout << "int\n";  }

int main()
{
    call( &print ); // prints "void"
}

只有其中一个重载函数符合被调用函数的原型,因此编译器知道。在上面std::bind的情况下,它无法说明,但你可以像我一样用强制转换强制它。

Lambdas或std::function可以包装任一成员函数,但请注意,不能在不同的std::function签名上重载函数。请参阅here

更新

处理问题的正确方法#3 - 让一个函数调用函数与你的签名截然不同 - 是使用像functor这样的中间函数(lambda,std::functionstd::bind ,手工编织器)以消除差异。

std::function<void()>具有相同签名的对象,无论您正在调用的实际函数是什么作为其签名。 std::function比lambda更昂贵(在存储和调用方面),但它具有一个类型名称,如果你需要将它存储在容器或其他东西中,你可以使用它。如果你正确地玩牌,Lambda有时可以被编译器内联,所以效率可能仍然有利于lambdas。

答案 1 :(得分:1)

  

由于有两个具有相同名称的函数(重载):print,当我传递类函数的地址:&Foo::print时,编译器如何知道我实际上正在调用{{1}但不是Foo::print(void)

由于[over.over] / p1:

,这是允许的
  

在某些上下文中,使用不带参数的重载函数名称来解析函数a   指向函数的指针或指向过载集中特定函数的成员函数的指针。

编译器可以使用parameter-type-list的目标类型来确定指向成员指针的重载集中的哪个函数:

  

在某些上下文中,使用不带参数的重载函数名称来解析函数a   指向函数的指针或指向过载集中特定函数的成员函数的指针。一个功能   模板名称被视为在此类上下文中命名一组重载函数。选择的功能   是一个类型与上下文中所需的目标类型的函数类型相同的类型。 [注意:..]目标可以是

     

- 正在初始化的对象或引用(8.5,8.5.3,8.5.4),
   - 作业的左侧(5.18),
   - 函数的参数(5.2.2),
   - [..]

名称Foo::print(int)表示编译器查找匹配的重载集。目标类型Foo:print存在于重载集中,因此编译器会将名称解析为该重载。

  

我是否可以通过其他方式概括上述代码,以便Foo::print(void)void call(Foo*, xxx)

可以传递Foo::print(void)的第二个参数

没有一般方法可以使用名称本身。该名称必须解决为重载。相反,尝试更改代码以接受像lambda这样的函数对象:

Foo::print(int)