【问题标题】:Binding class member function to c-function将类成员函数绑定到 c 函数
【发布时间】:2011-02-03 07:24:05
【问题描述】:

下面我有一个将类成员函数绑定到全局函数的概念。这样做的主要目的是使用 C++ 实现 C 风格的回调函数。能否以更好的方式完成(例如,不使用 final 宏或typeof,或使用 C++0x 功能)?

#include <iostream>

using namespace std;

template<typename MF> struct mf_wrapper_helper;

template<typename R, typename T>
struct mf_wrapper_helper<R (T::*)()>
{
    template <R (T::*F)()>
    static R wrapper(T *foo) { return (foo->*F)(); }
};

template<typename R, typename T, typename T1>
struct mf_wrapper_helper<R (T::*)(T1)>
{
    template <R (T::*F)(T1)>
    static R wrapper(T *foo, T1 arg1) { return (foo->*F)(arg1); }
};

#define BIND_MF(mf) \
    mf_wrapper_helper<typeof(mf)>::wrapper<mf>


struct Foo
{
    void x() { cout << "Foo::x()" << endl; }
    void x1(int i) { cout << "Foo::x1(" << i << ")" << endl; }
};

int
main()
{
    typedef void (*f_t)(Foo *);
    typedef void (*f1_t)(Foo *, int i);

    Foo foo;

    f_t f_p = BIND_MF(&Foo::x);
    (*f_p)(&foo);

    f1_t f1_p = BIND_MF(&Foo::x1);
    (*f1_p)(&foo, 314);

    return 0;
}

【问题讨论】:

  • 你用的是什么编译器?如果您可以使用 C++0x,只需使用无捕获的 lambda,它们就可以转换为函数指针。还有,为什么要动态分配main的东西?
  • 忽略new - 与问题无关。我使用 GCC 4.5 和 ICC 11.1。不确定 lambda 在这里有什么帮助,因为Lambda functions are function objects of an implementation-dependent type。其实我对C++0x不太了解,代码示例不胜感激。
  • 投票结束,因为这个问题可能更适合codereview.stackexchange.com
  • @klimkin:就像我说的,你可以制作一个就地 lambda,它可以转换为函数指针。我们只需要知道 C++0x 是否是一个选项。 “忽略那个新的 - 对这个问题无关紧要。”没错,为什么会在那里? :) @John:我不知道,也许如果它是一个官方交换网站,但现在它应该放在这里。 (忽略我认为代码审查网站是不必要的。)
  • 有一点值得一提,虽然不是真正的答案:C++0x 关键字 decltype 与 g++ 编译器扩展 typeof 基本相同。

标签: c++ templates


【解决方案1】:

我认为唯一可以完美工作的技术是为您希望在 C 回调中调用的每个成员函数编写一个 C 包装函数;即:

extern "C" void Foo_x(Foo *foo)
{
    foo->x();
}

extern "C" void Foo_x1(Foo *foo, int i)
{
    foo->x1(i);
}

您还可以使用 C++0x 的 lambda 表达式,它隐式转换为指向函数的指针,该函数具有与闭包类型的函数调用运算符相同的参数和返回类型。但是,请记住,函数类型的语言链接是“C++”,而不是“C”。

#include <cstdlib>
#include <iostream>

using namespace std;

struct Foo
{
    void x() { cout << "Foo::x()" << endl; }
    void x1(int i) { cout << "Foo::x1(" << i << ")" << endl; }
};

int main()
{
    typedef void (*f_t)(Foo*); // default ("C++") language linkage
    typedef void (*f1_t)(Foo*, int);

    Foo foo;

    Foo_x(&foo);
    Foo_x1(&foo, -10);

    f_t fn = [] (Foo *foo) {
        foo->x();
    };
    fn(&foo);

    f1_t fn1 = [] (Foo *foo, int i) {
        foo->x1(i);
    };
    fn1(&foo, 314);

    return EXIT_SUCCESS;
}

请注意,C++ 标准的第 5.2.2 节,函数调用指出:

通过表达式调用函数,该表达式的函数类型具有与被调用函数定义的函数类型的语言链接不同的语言链接。

因此,从技术上讲,以下内容会调用未定义的行为:

extern "C" typedef void (*f_t)(Foo*);

int main()
{
    Foo foo;

    f_t fn = [] (Foo *foo) {
        foo->x();
    };
    fn(&foo); // `fn` is a pointer to a function that uses "C++" language linkage,
            // but it is erroneously called through "C" language linkage.

//...

编辑:经过一些实验,我想出了以下模板函数,它们返回调用给定成员函数的 lambda:

template <typename return_t, class base, typename... arg_types>
std::function<return_t (base*, arg_types...)> make_lambda_to_call_member_function(return_t (base::*mem_fn)(arg_types...)) 
{
    return [mem_fn] (base *o, arg_types... args) -> return_t {
        (o->*mem_fn)(args...);
    };
}

template <typename return_t, class base, typename... arg_types>
std::function<return_t (const base*, arg_types...)> make_lambda_to_call_member_function(return_t (base::*cmem_fn)(arg_types...) const) 
{
    return [cmem_fn] (const base *o, arg_types... args) -> return_t {
        (o->*cmem_fn)(args...);
    };
}

如果Foo 定义为:

struct Foo
{
    void x() { cout << "Foo::x()" << endl; }
    void x1(int i) { cout << "Foo::x1(" << i << ")" << endl; }
    void cx1(float f) const { cout << "Foo::cx1(" << f << ")" << endl; }
};

然后你使用模板make_lambda_to_call_member_function like:

auto fn = make_lambda_to_call_member_function(&Foo::x);
fn(&foo);

auto fn1 = make_lambda_to_call_member_function(&Foo::x1);
fn1(&foo, 314);

auto fn2 = make_lambda_to_call_member_function(&Foo::cx1);
fn2(&foo, 44.832f);

但是请注意,返回的 lambda 对象不会隐式转换为函数指针,因为 lambda 表达式使用 lambda-capture。

C++0x 的最新草案,n3225,声明:

没有 lambda-capturelambda-expression 的闭包类型有一个公共的非虚拟非显式 const 转换函数,指向具有相同参数的函数的指针并返回类型作为闭包类型的函数调用运算符。这个转换函数的返回值应该是一个函数的地址,当被调用时,它与调用闭包类型的函数调用运算符具有相同的效果。

以下是非法的:

void (*fn5)(Foo*) = make_lambda_to_call_member_function(&Foo::x);

【讨论】:

  • 关于链接类型的观点很好。我发现使用 lambda 存在问题 - 所有参数类型都应完整列出。使用模板版本,所有类型都使用编译器计算。是否可以制作一个以成员函数为参数的lambda函数模板?
  • @klimkin:我用更多信息更新了我的答案。要回答您的问题,是的,可以编写一个模板函数,该函数返回一个调用给定成员函数的 lambda 对象,但该 lambda 对象不能再转换为函数指针。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-01-11
  • 2015-12-08
  • 1970-01-01
  • 1970-01-01
  • 2023-03-11
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多