使用 std::function 作为成员函数

Using std::function for member functions

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

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;
    }
}

现在有另一个函数应该根据其输入动态调用两个 class 方法之一:

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,当我传递class函数的地址时:&Foo::print,编译器是怎么知道的我实际上是在呼叫 Foo::print(void) 而不是 Foo::print(int)?

  2. 是否有另一种方法可以概括上面的代码,以便可以使用 Foo::print(void)Foo::print(int)[= 传递 void call(Foo*, xxx) 的第二个参数23=]

  3. 是否可以使用 C++11 中的新功能实现此功能 std::function?我知道为了将 std::function 与非静态 class 方法一起使用,我必须使用 std::bind 将每个 class 方法与特定的 class 绑定对象,但这对我来说效率太低了,因为我有很多 class 个对象要绑定。

首先,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 的正确方法——让一个函数调用函数具有与您的完全不同的签名——是使用一些像仿函数这样的中介(lambda,std::functionstd::bind, hand-rolled functor) 来消除差异。

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

Since there are two functions (overloaded) with the same name: print, when I pass the address of class function: &Foo::print, how did the compiler knows that I am actually calling Foo::print(void) but not Foo::print(int)?

这是允许的,因为 [over.over]/p1:

A use of an overloaded function name without arguments is resolved in certain contexts to a function, a pointer to function or a pointer to member function for a specific function from the overload set.

编译器可以使用参数类型列表的目标类型来确定指向成员的指针引用重载集中的哪个函数:

A use of an overloaded function name without arguments is resolved in certain contexts to a function, a pointer to function or a pointer to member function for a specific function from the overload set. A function template name is considered to name a set of overloaded functions in such contexts. The function selected is the one whose type is identical to the function type of the target type required in the context. [ Note: .. ] The target can be

     — an object or reference being initialized (8.5, 8.5.3, 8.5.4),
     — the left side of an assignment (5.18),
     — a parameter of a function (5.2.2),
     — [..]

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

Is there another way that I can generalize the code above such that the second parameter of void call(Foo*, xxx) can be passed using both Foo::print(void) and Foo::print(int)

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

template<class Callable>
void call(Foo* foo, Callable&& callback) {
    callback(foo);
}

int main()
{
    call(foo, [] (Foo* f) { f->print(); f->print(1); });
}