std::function的模板参数是如何工作的?(实现)

57

Bjarne Stroustrup的主页(C++11 FAQ)中:

struct X { int foo(int); };

std::function<int(X*, int)> f;
f = &X::foo; //pointer to member

X x;
int v = f(&x, 5); //call X::foo() for x with 5

它是如何工作的?std::function 如何调用 foo 成员函数

模板参数是 int(X*, int)&X::foo 是否从 成员函数指针 转换为 非成员函数指针?!

(int(*)(X*, int))&X::foo //casting (int(X::*)(int) to (int(*)(X*, int))

澄清一下:我知道使用 std::function 不需要转换任何指针,但我不知道 std::function 内部如何处理成员函数指针非成员函数指针之间的不兼容性。我不知道标准允许我们如何实现类似 std::function 的东西!


1
如果您在引用另一个网页,请始终提供链接。 - Martin York
1
页面在这里:http://www2.research.att.com/~bs/C++0xFAQ.html#std-function。Bjarne断言这是合法的,但没有提到它是如何工作的机制(无论是巧妙的模板化还是某种新的内置转换)。 - Tyler McHenry
5个回答

35

在获得其他答案和评论的帮助,阅读GCC源代码和C ++ 11标准后,我发现可以使用部分模板特化和函数重载解析函数类型(其返回类型和其参数类型)。

以下是一个简单(且不完整)的示例,用于实现类似于std :: function 的东西:

template<class T> class Function { };

// Parse the function type
template<class Res, class Obj, class... ArgTypes>
class Function<Res (Obj*, ArgTypes...)> {
    union Pointers {
        Res (*func)(Obj*, ArgTypes...);
        Res (Obj::*mem_func)(ArgTypes...);
    };

    typedef Res Callback(Pointers&, Obj&, ArgTypes...);

    Pointers ptrs;
    Callback* callback;

    static Res call_func(Pointers& ptrs, Obj& obj, ArgTypes... args) {
        return (*ptrs.func)(&obj, args...);
    }

    static Res call_mem_func(Pointers& ptrs, Obj& obj, ArgTypes... args) {
        return (obj.*(ptrs.mem_func))(args...);
    }

  public:

    Function() : callback(0) { }

    // Parse the function type
    Function(Res (*func)(Obj*, ArgTypes...)) {
        ptrs.func = func;
        callback = &call_func;
    }

    // Parse the function type
    Function(Res (Obj::*mem_func)(ArgTypes...)) {
        ptrs.mem_func = mem_func;
        callback = &call_mem_func;
    }

    Function(const Function& function) {
        ptrs = function.ptrs;
        callback = function.callback;
    }

    Function& operator=(const Function& function) {
        ptrs = function.ptrs;
        callback = function.callback;
        return *this;
    }

    Res operator()(Obj& obj, ArgTypes... args) {
        if(callback == 0) throw 0; // throw an exception
        return (*callback)(ptrs, obj, args...);
    }
};

使用方法:

#include <iostream>

struct Funny {
    void print(int i) {
        std::cout << "void (Funny::*)(int): " << i << std::endl;
    }
};

void print(Funny* funny, int i) {
    std::cout << "void (*)(Funny*, int): " << i << std::endl;
}

int main(int argc, char** argv) {
    Funny funny;
    Function<void(Funny*, int)> wmw;

    wmw = &Funny::print; // void (Funny::*)(int)
    wmw(funny, 10); // void (Funny::*)(int)

    wmw = &print; // void (*)(Funny*, int)
    wmw(funny, 8); // void (*)(Funny*, int)

    return 0;
}

3
希望这段代码里面没有隐藏着一个 switch 语句。 - Martin York
3
std::function可以接受函数对象,除了函数指针和成员函数指针之外。 - user102008

4
我相信它是这样实现的,但具体细节未定义(但我没有标准的副本)。
但考虑到需要涵盖的各种不同可能性,我觉得解密其确切定义将非常困难:所以我不打算尝试。
但我想你会想知道functor如何工作,它们相对简单。下面是一个快速示例。
Functors:
这些是像函数一样的对象。 它们在模板代码中非常有用,因为它们经常允许您可互换使用对象或函数。functor的好处是它们可以保存状态(一种类似于闭包的东西)。
struct X
{
     int operator()(int x) { return doStuff(x+1);}
     int doStuff(int x)    { return x+1;}
};

X   x;  // You can now use x like a function
int  a = x(5);

您可以利用函数对象持有状态的事实来保存诸如参数、对象或成员方法指针(或任意组合)等内容。

struct Y // Hold a member function pointer
{
    int (X::*member)(int x);
    int operator(X* obj, int param) { return (obj->*member)(param);}
};
X  x;
Y  y;
y.member = &X::doStuff;
int a = y(&x,5);

甚至可以进一步绑定参数。现在,您只需要提供其中一个参数即可。

struct Z
{
    int (X::*member)(int x);
    int  param;
    Z(int (X::*m)(int), int p) : member(m), param(p) {}

    int operator()(X* obj)  { return (obj->*member)(param);}
    int operator()(X& obj)  { return (obj.*member)(param);}
};

Z z(&X::doStuff,5);

X x;
int a = z(x);

谢谢提供信息,虽然我知道什么是函数对象(functor),并且我尝试了解:“std::function如何持有/调用成员/非成员函数指针?”但我失败了。 - Sadeq
它可能使用了许多模板特化。但是,持有函数指针与持有方法指针没有什么不同,只是类型不同而已。 - Martin York

3
回答标题中的问题。 std :: function 使用的参数是一种很好的技巧,可以将许多类型参数作为单个模板参数传递。这些参数是函数的参数类型和返回类型。
事实证明,std :: function 尝试对一般函数进行类型擦除,但这只是巧合。
事实上,曾经有一些编译器不接受这样的技巧,boost :: function 前身具有可移植语法,所有参数都可以分别传递:

Preferred syntax

boost::function<void(int*, int, int&, float&)> sum_avg;

Portable syntax

boost::function4<void, int*, int, int&, float&> sum_avg;
那么,这就是 std::function 的模板参数的工作原理,最终它只是一个技巧,使得许多参数看起来像函数调用。指向该类型函数的函数指针不一定涉及到该类。请参考:https://www.boost.org/doc/libs/1_68_0/doc/html/function/tutorial.html#id-1.3.16.5.4

2

它们不是函数指针。这就是 std::function 的存在原因。它可以包装任何可调用类型。您应该查看 boost::bind- 它经常用于使成员函数指针可调用为 (this,args)。


4
尽管如此,他的问题仍然是有效的。std::function 实例化参数是 int (X*, int),这与赋给它的 &X::foo 的类型不同。虽然可以清楚地看出如何在给定前者的参数的情况下调用后者,但从编译器的角度来看,这些类型并没有固有联系,因此这种做法为何被允许并不明显。需要注意避免改变原意。 - Tyler McHenry

2

看起来g++有一个union,可以保存函数指针、成员指针或指向functor的void指针。添加适当的重载标记哪个union成员是有效的并进行重型转换,然后它就可以工作了...


谢谢,我相信将成员函数指针转换为非成员函数指针是未定义的行为!通过使用联合,不需要进行转换。我应该测试一下。但是这个联合的成员如何仅由一个模板参数检测到(我们只为std :: function指定一个函数类型)? - Sadeq
"void指针可能指向一个函数对象,因此还需要对该函数对象进行内存管理?" - user102008

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接