C++成员函数指针指向全局函数指针

Posted

技术标签:

【中文标题】C++成员函数指针指向全局函数指针【英文标题】:C++ member function pointer to global function pointer 【发布时间】:2016-09-19 21:04:55 【问题描述】:

至少对我来说,我必须解决 C++ 中的一个棘手问题。有一个我无法修改的dll。它获取一个函数指针作为参数。如果我将指针传递给全局函数,一切正常。不幸的是,有一个要传递给 dll 的相同类对象的列表。在 C# 中,我通过使用委托解决了这个问题。这如何在 C++ 中完成?使用 std::function 不起作用。因此,在运行时会出现编码约定错误。进一步使用 MSVC2010 将是最佳选择。 我写了一个描述问题的示例:

#include <stdio.h>


// global function which works
void __stdcall task_global(float x, float y)  printf("Called global function with: %f %f\n", x, y); 
typedef void(__stdcall *f_pointer)(float, float);



// try of using a member function
class BaseTask 
public:
    virtual void __stdcall task(float x, float y) = 0;
;


class ListeningTask :public BaseTask 
public:
    void __stdcall task(float x, float y)  printf("Called this member function with: %f %f\n", x, y); 
;

typedef void (BaseTask::*member_f_pointer)(float, float);



// the dll to use
class RegisterTask 
public:
    // no posibility to access or modify!
    void __stdcall subscribe(f_pointer fp)  fp(1.0f, 2.0f); 
    // just for demonstration how to use a member function pointer
    void __stdcall subscribeMemberDemo(member_f_pointer mfp)   /*how to use mfp?*/;
;



int main() 

    RegisterTask register_task;

    // use global function
    f_pointer pointer_to_global_task = task_global;
    register_task.subscribe(pointer_to_global_task);


    /*---------------------------------------------------------------*/
    // use member function?
    std::list<ListeningTask> listening_task_list;
    for(int i = 0; i < 10; i++) 
        listening_task_list.push_back(ListeningTask lt);
        member_f_pointer pointer_to_member_task = &listening_task_list.back().task; //error C2276: '&': illegal operation on bound member function expression
        register_task.subscribeMemberDemo(pointer_to_member_task);

        // the tricky and important one to solve
        // how to pass the member function to this subscribe(f_pointer)?
        register_task.subscribe(pointer_to_member_task);
    

    getchar();
    return 0;

重要的问题是如何将成员函数指针传递给RegisterTask::subscribe(f_pointer)

括号中的问题是如何将成员函数传递给RegisterTask::subscribeMemberDemo(member_f_pointer)

希望有人能帮我解决这个问题?几天以来我一直在努力。

编辑: 我修改了问题以强调 ListenerTask 列表的问题。通过@pokey909 和@AndyG 的答案,我现在清楚了如何传递成员函数指针。它们都提供了一个指向一个对象的指针,或者更确切地说是一个对象列表。如果回调被调用,则一次调用ListenerTask 或所有std::list&lt;*ListenerTask&gt;。但是如何让列表中只有一个ListenerTask 被调用。 向 dll 传递多个回调。它 (RegisterTask) 可以做到这一点,因为以下具有全局函数的示例有效。

void __stdcall task_global_1(float x, float y)  printf("Called global function 1 with: %f %f\n", x, y); 
void __stdcall task_global_2(float x, float y)  printf("Called global function 2 with: %f %f\n", x, y); 
void __stdcall task_global_3(float x, float y)  printf("Called global function 3 with: %f %f\n", x, y); 

typedef void(__stdcall *f_pointer)(float, float);

int main() 
    // give the functions to the dll.
    f_pointer pointer_to_global_task_1 = task_global_1;
    register_task.subscribe(pointer_to_global_task_1);

    f_pointer pointer_to_global_task_2 = task_global_2;
    register_task.subscribe(pointer_to_global_task_2);

    f_pointer pointer_to_global_task_3 = task_global_3;
    register_task.subscribe(pointer_to_global_task_3);

共有三个全局函数指针。它们都被赋予了dll。现在,如果 dll 有task_global_2 的任务,它只会通知这个!如何通过成员函数指针实现这种区别?

注意: 我得到了 dll 的来源。希望这可以帮助。不幸的是,修改,构建是不可能的。这是回调定义

type TCallback = procedure( x : single; y : single; ); stdcall;

procedure subscribe(aCallback: TCallback ); StdCall;
begin
  TaskSocket.addTask( aCallback );
end;

procedure TSocket.addTask( aCallback : TCallback);
var newTask : TTask;
begin
  newTask := TTask.Create(aCallback);
  TaskList.addItem(newTask);
end;

【问题讨论】:

如果没有任何 C++11 特性,这对您来说将非常困难。基本上你会想要实现一个表单 std::bind 你不能。隐藏的this 参数让你明白。而是将调用您要执行的方法的静态方法传递到 dll 中。您必须找到某种方法来了解在静态方法中调用哪个对象。如果您只有一个对象实例,这将很容易。如果没有... 为什么RegisterTask::subscribe 没有声明为静态的?它不使用this 你已经抽象出了我怀疑的回调函数指针的细节。如果您还没有,请提供确切的签名。不是您认为的 importnt,而是传入的函数指针的确切签名。以及接受它的函数。 (最小化是好的,但很难!) Yakk 有一个很好的观点。回调注册通常采用void * 作为回调函数的参数,以便可以多路复用多个接收器。这很快解决了“我应该调用哪个对象?”问题。 【参考方案1】:

您可以使用一个独立的函数来调用将您的实例绑定到它的包装器。 这是一个粗略的例子

#include <iostream>
#include <string>
#include <functional>

// global function which works
std::function<void(float, float)> memberCb;
void task_global(float x, float y)  memberCb(x, y); 
typedef void(*f_pointer)(float, float);


// try of using a member function
class BaseTask 
public:
    virtual void  task(float x, float y) = 0;
;


class ListeningTask :public BaseTask 
public:
    void  task(float x, float y)  printf("Called this member function with: %f %f\n", x, y); 
;

typedef void (BaseTask::*member_f_pointer)(float, float);

void callbackWrapper(BaseTask* t, float x, float y)  t->task(x, y); 

// the dll to use
class RegisterTask 
public:
    // no posibility to access or modify!
    void  subscribe(f_pointer fp)  
        fp(1.0f, 2.0f); 
    
    // just for demonstration how to use a member function pointer
    void  subscribeMemberDemo(member_f_pointer mfp)   /*???*/ ;
;



int main() 

    RegisterTask register_task;

    ListeningTask listening_task;
    memberCb = std::bind(&callbackWrapper, &listening_task, std::placeholders::_1, std::placeholders::_2);
    register_task.subscribe(task_global);

    return 0;

注意

根据评论,我不确定是否所有这些都适用于 MSVC2010,因为我没有这个编译器版本。但是基本的 C++11 支持应该在那里。

编辑

我不确定这是否是您所追求的,但这会解决您的问题吗?

void callbackWrapper(const std::list<BaseTask*> &taskList, float x, float y) 
    for (auto t : taskList)
        t->task(x, y); 


int main() 

    RegisterTask register_task;

    std::list<BaseTask*> taskList;
    for (int i = 0; i < 4; ++i)
        taskList.push_back(new ListeningTask);
    memberCb = std::bind(&callbackWrapper, taskList, std::placeholders::_1, std::placeholders::_2);
    register_task.subscribe(task_global);

    return 0;

编辑 2 好的,我想我得到了你想要的。在不手动使用全局函数的情况下,我能想到的最好的方法是使用模板魔法。 但是请注意,它不像您希望的那样灵活,因为您必须在编译时绑定这些方法。 如果您需要在运行时添加它们,您可能可以使用相同的技巧但不使用模板。只需将所有 std::function 对象放在一个向量中,然后将其包装在一个单例或类似的东西中。

#include <iostream>
#include <string>
#include <functional>
#include <list>


/* Simulated DLL */
typedef void(*f_pointer)(float, float);
class RegisterTask 
public:
    void  subscribe(f_pointer fp) 
        fp(1.0f, 2.0f);
    
;



/* Static function generator to ease the pain to define all of them manually */
template<unsigned int T>
std::function<void(float, float)> &getWrapper() 
    static std::function<void(float, float)> fnc;
    return fnc;


/* Same here */
template<unsigned int T>
void task_global(float x, float y)  getWrapper<T>()(x, y); 



class BaseTask 
public:
    virtual void  task(float x, float y) = 0;
;
class ListeningTask :public BaseTask 
public:
    ListeningTask(int taskNum) : m_taskNum(taskNum) 
    void  task(float x, float y)  printf("Called this member of task %d function with: %f %f\n", getTaskNum(), x, y); 
    int getTaskNum() const  return m_taskNum; 
private:
    int m_taskNum;
;


/* Context injector */
void callbackWrapper(BaseTask* t, float x, float y) 
    t->task(x, y);


/* Convenience function to bind an instance to a task */
template<unsigned int T>
void bindTask(ListeningTask* t) 
    getWrapper<T>() = std::bind(&callbackWrapper, t, std::placeholders::_1, std::placeholders::_2);


int main() 

    RegisterTask register_task;

    auto task0 = new ListeningTask(1337);
    auto task1 = new ListeningTask(1984);
    auto task2 = new ListeningTask(42);

    bindTask<0>(task0);
    register_task.subscribe(task_global<0>);

    bindTask<1>(task1);
    register_task.subscribe(task_global<1>);

    bindTask<2>(task2);
    register_task.subscribe(task_global<2>);

    return 0;

Run Code demo

【讨论】:

我以为他说他使用的是早于 std::bind 的 C++ 版本。 @Barmar std::functionstd::bind 在 Visual Studio 2010 中。不确定它们的工作情况如何,因为 VS2010 的目标是一个尚未标准化的标准,但它们就在那里。编译器会跳过RegisterTask register_task;,但这是一个简单的修复。 在 VS2010 中 bind 在 tr1 命名空间中 (tr1::bind)。 感谢您的提醒。我实际上认为他可以使用 C++11,因为他使用了 C++11 样式初始化。但我更新了我的答案只是为了记录。 刚刚测试过。编译并运行。只需在变量声明中丢失【参考方案2】:

pokey909's answer 非常棒,但如果您甚至无法访问 std::functionstd::bind,我们可以破解它。

该方法的要点是,我们将定义一个模板类,并隐式转换为所需的函数类型。缺点是每个新的附加包装器都需要一个新的类型声明。

// assumes two function arguments
template<class Ret, class Mem, class Arg1, class Arg2, int>
struct MemBind

    typedef Ret(Mem::*mem_fn_type)(Arg1, Arg2);
    static void Set(mem_fn_type _fn, Mem* _instance)
    
        fn = _fn;
        instance = _instance;
    

    static Ret DoTheThing(Arg1 first, Arg2 second)
    
        return ((*instance).*fn)(first, second);
    

    typedef Ret(*fn_type)(Arg1, Arg2); 
    operator fn_type ()
    
        return DoTheThing;
    

    static mem_fn_type fn;
    static Mem* instance;
;

给定一些结构 Foo 和我们想要的回调:

struct Foo

    void Bar(float a, float b)
    
        std::cout << "Foo::Bar(float, float) " << a << " , " << b << std::endl;
    
;

我们必须定义我们的静态成员:

typedef MemBind<void, Foo, float, float, 0> MemBindZero;
template<> Foo* MemBindZero::instance = nullptr;
template<>  void(Foo::*MemBindZero::fn)(float, float)  = nullptr;

我们可以有一个接受函数指针的调用者:

void Caller(void(*_fn)(float, float))

    _fn(42.0, 1337.0);

这里的关键是MemBind 隐式转换为所需的函数类型。 MemBindZero 的 typedef 中的 0 允许我们为其他参数重用相同的类型,但在使用时将计数器增加到 1。我认为您可以将其替换为 __COUNTER__ 宏或类似的东西,但它是非标准的,所以我手动完成了。

接下来是创建MemBindZero的实例,然后设置static成员,最后将我们的实例传入Caller

 Foo f;
 MemBindZero bound;
 bound.Set(&Foo::Bar, &f);
 Caller(bound);

Demo


在演示中,我将静态成员初始化包装成一个更方便的宏:

#define MEMBIND(RET, CLASS, ARG1, ARG2, COUNT, ALIAS) \
typedef MemBind<RET, CLASS, ARG1, ARG2, COUNT> ALIAS; \
template<> CLASS * ALIAS::instance = nullptr; \
template<>  RET(CLASS::*ALIAS::fn)(ARG1, ARG2)  = nullptr;

所以我可以这样称呼它:

MEMBIND(void, Foo, float, float, 0, MemBindZero)
MEMBIND(void, OtherFoo, float, float, 1, MemBindOne)

【讨论】:

我知道我在这里完全把事情复杂化了。这主要是我在玩模板。 非常感谢!这适用于一个回调。如何使用std::list&lt;Foo&gt; 并将回调链接到其每个条目?我扩展了我的问题以更清楚地描述这一点。也许你可以看看它? 我不完全理解您的编辑。你想禁用一些回调吗?或者听起来你在问如何让订阅者只调用其中一个回调? 我想要对通过subscribe() 传递给dll 的每个ListenerTask 进行一次回调。就像在我的全局示例中一样,如果 dll 对 task_global_2 有什么事情要做,它只会通知这个。我需要将每个对象的非静态成员函数传递给 dll。所以dll可以区分传递的函数指针。我还能提供什么信息? @solarisx:DLL 现在如何区分回调?你提到你不能修改它,所以我们被它使用的任何逻辑所困。

以上是关于C++成员函数指针指向全局函数指针的主要内容,如果未能解决你的问题,请参考以下文章

C++|详解类成员指针:数据成员指针和成员函数指针及应用场合

c++怎样将指向【成员函数】的指针强转成指向【普通函数】的指针

c++类的成员函数指针如何转为普通指针

C++ 指向成员函数指针问题

从 C++ 的成员函数中获取指向成员函数的指针

C++指向对象成员函数的指针