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<*ListenerTask>
。但是如何让列表中只有一个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::function
和 std::bind
在 Visual Studio 2010 中。不确定它们的工作情况如何,因为 VS2010 的目标是一个尚未标准化的标准,但它们就在那里。编译器会跳过RegisterTask register_task;
,但这是一个简单的修复。
在 VS2010 中 bind
在 tr1 命名空间中 (tr1::bind
)。
感谢您的提醒。我实际上认为他可以使用 C++11,因为他使用了 C++11 样式初始化。但我更新了我的答案只是为了记录。
刚刚测试过。编译并运行。只需在变量声明中丢失
。【参考方案2】:
pokey909's answer 非常棒,但如果您甚至无法访问 std::function
和 std::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<Foo>
并将回调链接到其每个条目?我扩展了我的问题以更清楚地描述这一点。也许你可以看看它?
我不完全理解您的编辑。你想禁用一些回调吗?或者听起来你在问如何让订阅者只调用其中一个回调?
我想要对通过subscribe()
传递给dll 的每个ListenerTask
进行一次回调。就像在我的全局示例中一样,如果 dll 对 task_global_2
有什么事情要做,它只会通知这个。我需要将每个对象的非静态成员函数传递给 dll。所以dll可以区分传递的函数指针。我还能提供什么信息?
@solarisx:DLL 现在如何区分回调?你提到你不能修改它,所以我们被它使用的任何逻辑所困。以上是关于C++成员函数指针指向全局函数指针的主要内容,如果未能解决你的问题,请参考以下文章
C++|详解类成员指针:数据成员指针和成员函数指针及应用场合