C++成员函数指针指向全局函数指针
C++ member function pointer to global function pointer
我必须解决(至少对我而言)C++ 中的一个棘手问题。有一个我无法修改的dll。它获取一个函数指针作为参数。如果我将指针传递给全局函数,一切正常。不幸的是,有一个 相同 class 对象的列表要传递给 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;
您可以使用独立函数来调用将您的实例绑定到它的包装器。
这是粗略的例子
#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;
}
非常棒,但如果您甚至无法访问 std::function
和 std::bind
,我们可以绕过它。
该方法的要点是我们将定义一个模板 class,该模板可以隐式转换为所需的函数类型。缺点是每个新的附加包装器都需要一个新的类型声明。
// 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);
在演示中我将静态成员初始化包装成一个更方便的宏:
#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)
我必须解决(至少对我而言)C++ 中的一个棘手问题。有一个我无法修改的dll。它获取一个函数指针作为参数。如果我将指针传递给全局函数,一切正常。不幸的是,有一个 相同 class 对象的列表要传递给 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;
您可以使用独立函数来调用将您的实例绑定到它的包装器。 这是粗略的例子
#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;
}
std::function
和 std::bind
,我们可以绕过它。
该方法的要点是我们将定义一个模板 class,该模板可以隐式转换为所需的函数类型。缺点是每个新的附加包装器都需要一个新的类型声明。
// 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);
在演示中我将静态成员初始化包装成一个更方便的宏:
#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)