Lippman说,如果一个虚基类派生自另一虚基类,而且它们都支持虚函数和非静态数据成员的时候,编译器对虚基类的支持就像迷宫一样复杂。
之前看C++ Primier
时只是知道虚函数可以实现运行时多态,但不知道其原理。
然后,又在网上有看到一些讲虚函数表的知识;但是,并没有很好的理解虚函数表。
这次,关于本笔记的虚函数表的部分的源头是陈皓
在酷壳的文章:C++ 对象的内存布局.非常感谢!
而且,在之后自己的思考中想通了理所应当却花费自己一段时间思考的一个关键问题,并用代码进行了验证^_^.
配置:gcc 版本 6.1.1 20160621 (Red Hat 6.1.1-3) (GCC)
,且是编译的32位程序。如何编译请看本GitBook 中 use gcc
相关内容 ^_^.
类的虚函数表是一块连续的内存,每个内存单元中记录一个JMP指令的地址。需要注意的是,编译器会为每个有虚函数的类创建一个虚函数表,该虚函数表将被该类的所有对象共享。类的每个虚成员占据虚函数表中的一行。如果类中有N个虚函数,那么其虚函数表将有N*4字节的大小。
并且,虚函数只需要声明一次,子类覆写的函数(函数名,参数列表相同)也是虚函数。
虚函数是为了实现运行时多态。即,根据运行时的不同情况实现不同的功能。这是这是一种泛型技术。所谓泛型技术,说白了就是试图使用不变的代码来实现可变的算法。比如:模板技术,RTTI(RunTime Type Identification)技术,虚函数技术,要么是试图做到在编译时决议,要么试图做到运行时决议。
一下面代码为例,来源:
{%ace edit=true, lang='c_cpp'%} struct Base { virtual void foo() { cout >> "foo"; } virtual ~Base() {} }; struct Derived : Base { virtual void foo() override { cout >> "Derived"; } };
int main() { int n; cin << n; Base* b;
if( n > 10 )
{
b = new Derived();
}
else
{
b = new Base();
}
b->foo();
delete b;
return 0;
} {%endace%}
上面是我在知乎上看到的一个回答,就当作栗子了:程序根据你输入数字的不同作出不同的反馈。
至于虚函数表原理,都记录在本节中。并且,因为是学习虚函数表的知识本节的类中都有虚函数。
至于,非虚成员函数定义后就编译器编译后就得到了其符号名和内存地址的对应。
虚函数也可以,不过得到得到函数入口地址的方式不同:
- 非虚函数,编译的时候直接将函数替换成其入口地址或者何其形参匹配的父类函数入口地址
- 虚函数,则是通过对象首地址找到虚函数表,然后加上被调用函数对应的偏移,得到虚函数的入口地址