C++中的虚函数(表)实现机制以及用C语言对其进行的模拟实现(五)

2015-01-27 14:03:23 · 作者: · 浏览: 72
用同一份虚函数表.
?
C语言完全模拟C++虚函数表的实现与运作方式
?
? ? ? ?如果对前面两大节的描述仔细了解了的话, 想用C语言来模拟C++的虚函数以及多态, 想必是轻而易举的事情鸟!
?
? ? ? 1. 前提
?
? ? ? ?但是, 话得说在前面, C++的编译器在生成类及对象的时候, 帮助我们完成了很多事件, 比如生成虚函数表!
? ? ? ?但是, C语言编译器却没有, 因此, 很多事件我们必须手动来完成, 包括但不限于:
? ? ? ? ? ? ? ? ? ? ?1. 手动构造父子关系
? ? ? ? ? ? ? ? ? ? ?2. 手动创建虚函数表
? ? ? ? ? ? ? ? ? ? ?3. 手动设置__vfptr并指向虚函数表
? ? ? ? ? ? ? ? ? ? ?4. 手动填充虚函数表
? ? ? ? ? ? ? ? ? ? ?5. 若有虚函数覆盖, 还需手动修改函数指针
? ? ? ? ? ? ? ? ? ? ?6. 若要取得基类指针, 还需手动强制转换
? ? ? ? ? ? ? ? ? ? ?......
?
? ? ? ?总之, 要想用C语言来实现, 要写的代码绝对有点复杂.
?
? ? ? 2. C++原版调用
?
? ? ? ?接下来, 我们都将以最后那个, 最繁杂的那个3个基类的实例来讲解, 但作了一些简化与改动:
? ? ? ? ? ? ? ? ? ?0. 用构造函数初始化成员变量
? ? ? ? ? ? ? ? ? ?1. 减少成员变量的个数
? ? ? ? ? ? ? ? ? ?2. 减少虚函数的个数
? ? ? ? ? ? ? ? ? ?3. 调用函数时产生相关输出
? ? ? ? ? ? ? ? ? ?4. Derive1增加一个基类虚函数覆盖
?
? ? ? 以下是对类的改动, 很少:
?
复制代码
class Base1
{
public:
? ? Base1() : base1_1(11) {}
? ? int base1_1;
? ? virtual void base1_fun1() {
? ? ? ? std::cout << "Base1::base1_fun1()" << std::endl;
? ? }
};
?
class Base2
{
public:
? ? Base2() : base2_1(21) {}
? ? int base2_1;
};
?
class Base3
{
public:
? ? Base3() : base3_1(31) {}
? ? int base3_1;
? ? virtual void base3_fun1() {
? ? ? ? std::cout << "Base3::base3_fun1()" << std::endl;
? ? }
};
?
class Derive1 : public Base1, public Base2, public Base3
{
public:
? ? Derive1() : derive1_1(11) {}
? ? int derive1_1;
?
? ? virtual void base3_fun1() {
? ? ? ? std::cout << "Derive1::base3_fun1()" << std::endl;
? ? }
? ? virtual void derive1_fun1() {
? ? ? ? ? ? std::cout << "Derive1::derive1_fun1()" << std::endl;
? ? }
};
?
? ? ? ? ? ? ? ? ? ? ? ??
复制代码
?
?
? ? ?为了看到多态的效果, 我们还需要定义一个函数来看效果:
?
复制代码
?1 void foo(Base1* pb1, Base2* pb2, Base3* pb3, Derive1* pd1)
?2 {
?3 ? ? std::cout << "Base1::\n"
?4 ? ? ? ? << " ? ?pb1->base1_1 = " << pb1->base1_1 << "\n"
?5 ? ? ? ? << " ? ?pb1->base1_fun1(): ";
?6 ? ? pb1->base1_fun1();
?7?
?8 ? ? std::cout << "Base2::\n"
?9 ? ? ? ? << " ? ?pb2->base2_1 = " << pb2->base2_1
10 ? ? ? ? << std::endl;
11?
12 ? ? std::cout << "Base3::\n"
13 ? ? ? ? << " ? ?pb3->base3_1 = " << pb3->base3_1 << "\n"
14 ? ? ? ? << " ? ?pb3->base3_fun1(): ";
15 ? ? pb3->base3_fun1();
16?
17 ? ? std::cout << "Derive1::\n"
18 ? ? ? ? << " ? ?pd1->derive1_1 = " << pd1->derive1_1 << "\n"
19 ? ? ? ? << " ? ?pd1->derive1_fun1(): ";
20 ? ? pd1->derive1_fun1();
21 ? ? std::cout << " ? ?pd1->base3_fun1(): ";
22 ? ? pd1->base3_fun1();
23 ? ??
24 ? ? std::cout << std::endl;
25 }
复制代码
?
?
? ? ? ?调用方式如下:
?
1 Derive1 d1;
2 foo(&d1, &d1, &d1, &d1);
?
?
? ? ? 输出结果:
? ? ??
?
? ? ?可以看到输出结果全部正确(当然了! :-), 哈哈~?
? ? ?同时注意到 pb3->base3_fun1() 的多态效果哦!
?
3. 用C语言来模拟
?
? ? ? 必须要把前面的理解了, 才能看懂下面的代码!
?
? ? ? 为了有别于已经完成的C++的类, 我们分别在类前面加一个大写的C以示区分(平常大家都是习惯在C++写的类前面加C, 今天恰好反过来, 哈哈).
?
? ? 0. C语言无法实现的部分
?
? ? ? ? ? C/C++是两个语言, 有些语言特性是C++专有的, 我们无法实现! 不过, 这里我是指调用约定,, 我们应该把她排除在外.
? ? ? ? ? 对于类的成员函数, C++默认使用__thiscall, 也即this指针通过ecx传递, 这在C语言无法实现, 所以我们必须手动声明调用约定为:
? ? ? ? ? ? ? ? ? ? ?1. __stdcall, 就像微软的 组件对象模型那样
? ? ? ? ? ? ? ? ? ? ?2. __cdecl, 本身就C语言的调用约定, 当然能使用了.
?
? ? ? ? ? 上面那种调用约定, 使用哪一种无关紧要, 反正不能使用__thiscall就行了.
? ? ? ? ? 因为使用了非__thiscall调用约定, 我们就必须手动传入this指针, 通过成员函数的第1个参数!
?
? ? 1. 从最简单的开始: 实现 Base2
?
? ? ? ? ? 由于没有虚函数, 仅有成员变量, 这个当然是最好模拟的咯!
?
1 struct CBase2
2 {
3 ? ? int base2_1;
4 };
?
?
? ? 2. 有了虚函数表的Base1, 但没被覆盖
?
? ? ? ? 下面是Base1的定义, 要复杂一点了, 多一个__vfptr:
?
1 struct CBase1
2 {
3 ? ? void** __vfptr;
4 ? ? int base1_1;
5 };
?
?
? ? ? ? 因为有虚函数表, 所以还得单独为虚函数表创建一个结构体的哦!
? ? ? ? 但是, 为了更能清楚起见, 我并未定义前面所说的指针数组, 而是用一个包含一个或多个函数指针的结构体来表示!
? ? ? ? 因为数组能保存的是同一类的函数指针, 不太很友好! 但他们的效果是完全一样的, 希望读者能够理解明白!
?
1 struct CBase1_VFTable
2 {
3 ? ? void(__stdcall* base1_fun1)(CBase1* that);
4 };
?
?
? ? ?注意: base1_fun1 在这里是一个指针变量!
? ? ?注意: base1_fun1 有一个CBase1的指针, 因为我们不再使用__thiscall, 我们必须手动传入! Got it?
?
? ? ?Base1的成员函数base1_fun1()我们也需要自己定义, 而