【问题标题】:How does the template parameter of std::function work? (implementation)std::function 的模板参数是如何工作的? (执行)
【发布时间】:2011-04-01 21:05:49
【问题描述】:

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),是&amp;X::foo成员函数指针转成非成员函数指针?!

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

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

【问题讨论】:

  • 如果您指的是另一个网页。请始终提供链接。
  • 页面在这里:www2.research.att.com/~bs/C++0xFAQ.html#std-function。 Bjarne 声称这是合法的,但没有提及其工作原理(无论是其巧妙的模板,还是某种新的内置转换)。

标签: c++ templates c++11 function-pointers function-object


【解决方案1】:

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

【讨论】:

  • 他的问题仍然有效。 std::function 实例化使用int (X*, int) 进行参数化,这与分配给它的&amp;X::foo 的类型不同。虽然很清楚如何在给定前者的参数的情况下调用后者,但从编译器的角度来看,这些类型本质上并不相关,因此如何允许这样做并不明显。
【解决方案2】:

它是如何做到的(我相信)未定义(但我这里没有标准的副本)。

但考虑到需要涵盖的所有不同可能性,我感觉要破译其工作原理的确切定义非常困难:所以我不打算尝试。

但我想你想知道函子是如何工作的,而且它们相对简单。所以这里是一个简单的例子。

函子:

这些对象的作用类似于函数。
它们在模板代码中非常有用,因为它们通常允许您互换使用对象或函数。不过,函子的伟大之处在于它们可以保持状态(一种穷人的闭包)。

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

【讨论】:

  • 感谢您的信息,尽管我知道函子(函数对象)是什么,并且我想知道:“std::function 如何保存/调用成员/非成员函数指针? "我做不到。
  • 它可能使用了大量的模板特化。但是持有一个函数指针和持有一个方法指针并没有什么不同,只是类型不同而已。
【解决方案3】:

g++ 似乎有一个联合,它可以保留可能指向函子的函数指针、成员指针或 void 指针。添加重载,适当地标记哪个联合成员是有效的并且重铸到汤中,然后它就可以工作了......

【讨论】:

  • 谢谢,我相信将成员函数指针转换为非成员函数指针是未定义的行为!并且通过使用联合,不需要强制转换。我应该测试一下。但是这个联合的成员是如何只被一个模板参数检测到的(我们只为 std::function 指定一种函数类型)?
  • "void 指针,它可能是指向一个函子的指针" 那么它还需要以某种方式对这个函子进行内存管理?
【解决方案4】:

在得到其他答案和 cmets 的帮助,阅读 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;
}

【讨论】:

  • 如果想在这段代码中隐藏了一个 switch 语句,那会让人讨厌。
  • @Martin York:谢谢,我改了。
  • std::function 也接受函数对象,除了函数指针和成员函数指针
【解决方案5】:

回答标题中的问题。 std::function 使用的参数是一个很好的技巧,可以将许多类型参数作为单个模板参数传递。这些参数是参数类型和函数的返回类型。

事实证明,std::function 试图对通用函子进行类型擦除,但这只是巧合。

事实上,曾几何时有些编译器不接受这种技巧,而 boost::function 的前身有一个 portable 语法,所有参数都可以通过它单独传递:

首选语法

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

可移植语法

boost::function4<void, int*, int, int&, float&> sum_avg;

https://www.boost.org/doc/libs/1_68_0/doc/html/function/tutorial.html#id-1.3.16.5.4

所以std::function 的模板参数就是这样工作的,最后只是让很多参数看起来像一个函数调用。指向该类型函数的函数指针不一定包含在类中。

【讨论】:

    猜你喜欢
    • 2014-01-03
    • 2021-10-30
    • 1970-01-01
    • 1970-01-01
    • 2017-10-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多