引言
今天群友的一发提问让我想起了很久之前自己碰到的类似的问题。大概可以这么提问来描述这个问题的本质:怎么把类成员函数变成普通的函指调用?
问题点
- 类成员函数怎么变成普通的函数指针?
- 隐藏的this指针怎么传递?
C++标准库的解决方案
自从C++11之后,这都不是事。使用std::function、std::bind即可解决,大概是这样的:
#include <functional>
#include <iostream>
using namespace std;
class Myclass
{
public:
void func(int a){cout<<a<<endl;}
}
int main()
{
Myclass a;
auto global_func = bind(&Myclass::func,&a,placeholders::_1);
global_func();
}
通过std::bind把类成员函数包装成一个std::function对象,这个对象可以像使用普通的函数一样调用。
花式解决方案
问题描述
如果就使用C++标准库搞定没啥意思哈。很久之前我就想过这个问题,因为可以用标准库解决没有去深思,今天我突然想到可以使用汇编解决。其实这个问题不好解决的原因就在于隐藏的this指针没办法显示传递。当然这是在你不知道类定义,用void*泛型编程的前提下。我大概是这个意思:
class Myclass
{
public:
void func(){}
}
int main()
{
Myclass a;
using GlobalFunc = void(*)();
GlobalFunc gFunc = &Myclass::func;//这也不行,类成员函数的指针不能赋给全局类型的函指
gFunc();//这显然不行,都没有绑定对象
}
显然不用标准库,我们会碰到上面所述的2个问题点。
类成员函数转成普通的函数指针
这边引用我之前的文章用联合体获取类成员函数地址中的方法
template<typename dst_type, typename src_type>
dst_type union_cast(src_type src)
{
union {
src_type s;
dst_type d;
}u;
u.s = src;
return u.d;
}
this指针传递
这一部分需要用到汇编,思路就是我们手动用汇编代码去调用成员函数。汇编里面没有什么类的概念,只要知道函数地址,参数就可以调用。通过反汇编调试,我发现在VS编译器中(我使用的是VS2019),x86平台下this指针地址需要赋值给ecx,x64平台下需要赋值给rcx。下面我贴上完整的代码。
#include <iostream>
using namespace std;
using DstType = void(*)(void);
class ABC
{
int data=1;
float data2 = 3.0;
public:
void operator()()
{
cout << data << endl;
cout << data2 << endl;
};
void func2()
{
int c = 0;
int b = 1;
cout << b + c + data << endl;
}
};
class MyStdFunction
{
public:
MyStdFunction(void* ptr_obj, DstType ptr_func) :ptr_obj(ptr_obj), ptr_func(ptr_func) {}
void operator()()
{
__asm
{
push ecx
push eax
push ebx
mov ebx, dword ptr[this]
mov ecx, dword ptr[ebx]
mov eax, dword ptr[ebx + 4]
call eax
pop eax
pop ecx
pop ebx
}
}
private:
void* ptr_obj;
DstType ptr_func;
};
template<typename dst_type, typename src_type>
dst_type union_cast(src_type src)
{
union {
src_type s;
dst_type d;
}u;
u.s = src;
return u.d;
}
template<typename src_type>
MyStdFunction bind(src_type func,void* ptr_obj)
{
DstType dst_func = union_cast<DstType>(func);
return MyStdFunction(ptr_obj, dst_func);
}
int main()
{
ABC a;
MyStdFunction b = ::bind(&ABC::func2, &a);
b();
}
总结
最终我们使用联合体+汇编实现了类成员函数转普通函指的调用方式(仅限VS x86,更换编译器或平台需要重写汇编)。虽然这就是玩玩的,但是对于C++会有更深刻一点的认识吧。多年之前没有想到的解决方法,今日灵光乍现,这感觉真的很爽。这也许就是代码的魅力所在吧。