为了简化,只考虑有虚函数的单继承的情况。

假如想在C中实现以下在C++中的代码,应该如何做呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Base {
public:
int x;

virtual ~Base() = default;
void baseFunc();
virtual void vfunc1();
virtual void vfunc2();
};

void Base::baseFunc() { printf("This is Base::baseFunc(no virtual), x = %d\n", x); }
void Base::vfunc1() { printf("This is Base::vfunc1(virtual), x = %d\n", x); }
void Base::vfunc2() { printf("This is Base::vfunc2(virtual), x = %d\n", x); }

class Derived : public Base {
public:
int y;

void derivedFunc();
void vfunc1();
virtual void vfunc3();
};

void Derived::derivedFunc() { printf("This is Derived::derivedFunc(no virtual), y = %d\n", y); }
void Derived::vfunc1() { printf("This is Derived::vfunc1(virtual), y = %d\n", y); }
void Derived::vfunc3() { printf("This is Derived::vfunc3(virtual), y = %d\n", y); }

首先看下两个类大致的一个对象模型

01

我们知道,在C++中要利用多态的性质必须利用指针或引用实现

1
2
3
4
5
Base *base = new Derived;
base->vfunc1(); // 调用Derived::vfunc1()
base->vfunc2(); // 调用Base::vfunc2()
// base->vfunc3(); error: 'class Base' has no member named 'vfunc3'
// base->derivedFunc(); error: 'class Base' has no member named 'derivedFunc'

可以发现,当基类指针指向一个子类时会发生内存切割,大致意思就是,虽然我是Derived类,但我只能访问到Base的那一部分内容,也就是下图中红色的部分

02

至于为什么不能不用指针或引用,可以看下该文章:oop - Why we can’t implement polymorphism in C++ without base class pointer or reference? - Stack Overflow

简单来说就是指针和引用只是说明了所指向的内存的大小,并没有规定类型,所以具体类型是看右边。否则具体类型看左边,一旦被固定就无法改变。

我的实现方式是在基类中定义一个指向虚表的指针,根据虚函数数量定义虚表大小,虚表里就是一个个槽,每个槽放置相应的函数地址。然后子类会在自己内部定义一个父类,相当于给父类的位置,子类会用父类的虚表,根据自己函数重写来修改虚表内容,为了方便就不考虑虚析构函数的实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
#include <stdio.h>
#include <stdlib.h>

struct Base {
// 指向虚表的指针
void **vptr;

int x;

// Base类的非多态方法
void (*baseFunc)(struct Base *const);
};

// 对于所有non static的成员函数,在C++中都会隐式的给参数列表中加上一个指向自己的指针,并且不能赋给该指针其他对象。
void baseFunc(struct Base *const this) { printf("This is Base Function(no virtual), x = %d\n", this->x); }
// 由于C语言无法重载,所以将vfunc后面加上后缀来表明是谁的虚函数
void vfunc1_base(struct Base *const this) { printf("This is Base::vfunc1(virtual), x = %d\n", this->x); }
void vfunc2_base(struct Base *const this) { printf("This is Base::vfunc2(virtual), x = %d\n", this->x); }


struct Base *newBase(int x) {
struct Base *base = (struct Base *) malloc(sizeof(struct Base));
base->x = x;
base->baseFunc = &baseFunc;
base->vptr = (void *) malloc(sizeof(void *) * 2);
(base->vptr)[0] = &vfunc1_base;
(base->vptr)[1] = &vfunc2_base;
return base;
}

struct Derived {
struct Base base;

int y;

// Derived类的非多态方法
void (*derivedFunc)(struct Derived *const);
};

void derivedFunc(struct Derived *const this) { printf("This is Derived::derivedFunc(non virtual), y = %d\n", this->y); }
void vfunc1_derived(struct Derived *const this) { printf("This is Derived::vfunc1(virtual), y = %d\n", this->y); }
void vfunc3_derived(struct Derived *const this) { printf("This is Derived::vfunc3(virtual), y = %d\n", this->y); }

struct Derived *newDerived(int y) {
struct Derived *derived = (struct Derived *) malloc(sizeof(struct Derived));
derived->y = y;
(derived->base.baseFunc) = &baseFunc;
derived->base.vptr = (void *) malloc(sizeof(void *) * 3);
(derived->base.vptr)[0] = &vfunc1_derived;
(derived->base.vptr)[1] = &vfunc2_base;
(derived->base.vptr)[2] = &vfunc3_derived;
derived->derivedFunc = &derivedFunc;
return derived;
}

int main() {
struct Base *ptr = (struct Base *) newDerived(10);

ptr->baseFunc(ptr);
// 因为虚表中存的是void *类型,所以要强制转换类型成函数指针。
// vfunc1_derived()
((void (*)(struct Base *)) ((ptr->vptr)[0]))(ptr);
// vfunc2_base()
((void (*)(struct Base *)) ((ptr->vptr)[1]))(ptr);

// 实际在C++中无法访问到vfunc3_derived(),但在这里可以,可以考虑下原因。
// vfunc3_derived()
((void (*)(struct Base *)) ((ptr->vptr)[2]))(ptr);
return 0;
}

最后的结果输出

1
2
3
4
This is Base Function(no virtual), x = 0
This is Derived::vfunc1(virtual), y = 10
This is Base::vfunc2(virtual), x = 0
This is Derived::vfunc3(virtual), y = 10

基本上实现了多态,只是还不是那么的完善,还有很多漏洞。