C++基础知识之"多态原理"

fengjingtu

理论知识:

当类中声明虚函数时,编译器会在类中生成一个虚函数表

虚函数表是一个存储类成员函数指针的数据结构

虚函数表是由编译器自动生成与维护的

virtual成员函数会被编译器放入虚函数表中

当存在虚函数时,每个对象中都有一个指向虚函数表的指针(C++编译器给父类对象、子类对象提前布局vptr指针;当执行函数是,C++编译器不需要区分子类对象或者父类对象,只需要在base指针中,找vptr指针即可。)

VPTR一般作为类对象的第一个成员

多态的实现原理

C++中多态的实现原理

当类中声明虚函数时,编译器会在类中生成一个虚函数表

虚函数表是一个存储类成员函数指针的数据结构

虚函数表是由编译器自动生成与维护的

virtual成员函数会被编译器放入虚函数表中

存在虚函数时,每个对象中都有一个指向虚函数表的指针(vptr指针)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class parent
{
public:
virtual void func()
{
cout<<"Parent::func()"<<endl;
}
virtual void func(int i)
{
cout<<"Parent::func(int i)"<<endl;
}
};
class child :public parent
{
public:
virtual void func()
{
cout<<"Child::func()"<<endl;
}
virtual void func(int i)
{
cout<<"Child::func(int i)"<<endl;
}
};

parent对象:

1
2
3
VTABLE
void parent::func()
void parent::func(int i)

child对象

1
2
3
VTABLE
void child::func()
void child::func(int i)
1
2
3
4
5
6
7
8
9
10
void run(parent *p)
{
p->func();
}
/*
编译器确定func是否为虚函数
1、func不是虚函数,编译器可以直接确定被调用的成员函数,(静态链编,根据parent类型来确定)
2、func是虚函数,编译器根据对象p的vptr指针,所指的虚函数表中参照func()函数,并调用。
注意:查找和调用在运行时完成(实现所谓的动态链编)
*/

说明1:

通过虚函数表指针VPTR调用重写函数是在程序运行时进行的,因此需要通过寻址操作才能确定真正应该调用的函数。而普通成员函数是在编译时就确定了调用的函数。在效率上,虚函数的效率要低很多。

说明2:

出于效率考虑,没有必要将所有成员函数都声明为虚函数

说明3 :C++编译器,执行函数,不需要区分是子类对象还是父类对象

如果p调用的函数是虚函数,就去找p,再找vptr,虚函数表,执行函数,所以C++编译器不需要知道是子类对象还是父类对象,给我们的程序员造成的假象是C++编译器能识别子类对象或者父类对象。

如果是普通函数,就根据类型,在静态链编的时候,就确定要调用的函数。

如何证明vptr指针的存在

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

#include <iostream>

using namespace std;
//vptr指针是指向虚函数表的指针,每个虚函数表在类的最前面
class Parents2
{
public:
void print()
{
cout << "I am parents" << endl;
}
virtual void print2()
{
cout << "I am 2 parents" << endl;
}
int a;
};
class Parents3
{
public:
void print()
{
cout << "I am parents" << endl;
}
void print2()
{
cout << "I am 2 parents" << endl;
}
int a;
};

void vfptr_exist()
{
Parents2 p2;
Parents3 p3;
cout << "sizeof(p2)=" << sizeof(p2) << endl;
cout << "sizeof(p3)=" << sizeof(p3) << endl;
}

构造函数中能调用虚函数,实现多态吗

对象中的VPTR指针什么时候被初始化?

对象在创建的时,由编译器对VPTR指针进行初始化

只有当对象的构造完全结束后VPTR的指向才最终确定

父类对象的VPTR指向父类虚函数表

子类对象的VPTR指向子类虚函数表

要初始化一个子类的vptr指针,这个初始化时分步骤的:

当执行父类的构造函数时,子类对象的vptr指向的是父类的虚函数表,当父类的构造函数运行完毕后,会把vptr指向子类的虚函数表。

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

#include <iostream>

using namespace std;

//构造函数中能调用虚函数,实现多态吗
class Parents5
{
public:
Parents5()
{
a = 0;
print2();
}
void print()
{
cout << "I am parents" << endl;
}
virtual void print2()
{
cout << "I am 2 parents" << endl;
}
int a;
};
class Childs2 :public Parents5
{
public:
Childs2()
{
a = 0;
print2();
}
void print()
{
cout << "I am childs" << endl;
}
virtual void print2()
{
cout << "I am 2 childs" << endl;
}
int a;
};
void howtoConstruct(Parents5 *p)//在用c2初始化形参的时候,第一次打印的是parents,没有实现多态,只有当对象的构造完全结束的时候,才能确定vfptr指针的最终指向
{
p->print2();
}
void construct_virtual()
{
Childs2 c2;
howtoConstruct(&c2);
}

int main_construct_virtual()
{
construct_virtual();
cout << "Hello world!" << endl;
system("pause");
return 0;
}

虚析构函数

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
71
72
73
74
75
76
77

#include <iostream>
using namespace std;

//使用一个指针,释放对象所有资源

class Base1
{
public:
Base1()
{
p = new int;
*p = 5;
cout << "Base1" << endl;
}
~Base1()
{
cout << "hello" << endl;
delete p;
cout << "~Base1" << endl;
};
int *p;
};
class Base2
{
public:
Base2()
{
p2 = new int;
*p2 = 5;
cout << "Base2" << endl;
}
virtual ~Base2()
{
delete p2;
cout << "~Base2" << endl;
};
int *p2;
};
class Base3 :public Base1, public Base2
{
public:
Base3()
{
p3 = new int;
*p3 = 5;
cout << "Base3" << endl;
}
~Base3()
{
delete p3;
cout << "~Base3" << endl;
};
int *p3;
};

void destroy_virtual()
{
//动态建立
//想用父类指针释放所有资源。
Base3 *b = new Base3;
delete b;
cout << "Hello world!" << endl;
Base2 *b1 = new Base3;
delete b1;
cout << "Hello world!" << endl;
Base1 *b2 = new Base3;
//delete b2;//不会释放base1的空间,因为不是virtual,有的编译器报错
cout << "Hello world!" << endl;
}
int main_destroy_virtual()
{
destroy_virtual();
cout << "Hello world!" << endl;
system("pause");
return 0;
}