C++对象模型:g++的实现(五)
作者:互联网
1. 单一继承体系下的虚函数
在前面的博客中我们已经通过对虚表的探索讲了虚函数的一般实现,大体上来说就是编译器会在适当的时候(在单一继承体系中就是当类中第一次出现虚函数的时候)添加一个虚表指针,指向属于该类的虚函数表,而所有虚函数的地址会出现在虚表指针的固定表项,也就是说在继承体系下的一个虚函数会被赋予固定的虚表下标。当派生类覆写(override)了基类的虚函数时,新的虚函数的地址会出现在基类虚函数在虚表中的位置,在多态调用虚函数时从虚表中取出虚函数地址来调用,从而实现多态。
一般而言,在单一继承体系下每一个类都只有一个虚表,在这个虚表中存有所有active virtual functions(中文版《深度探索C++对象模型》没有翻译,我这里也直接使用了,在我的理解里就是派生类所有有效的、能用的虚函数)的地址。这些active virtual functions包括:
- 该类所定义的所有虚函数,包括其覆写(override)的基类的虚函数;
- 继承自基类的虚函数,如果派生类不覆写这些虚函数的话;
- 一个pure_vairtual_called()函数实体,她既可以扮演pure virtual function的空间保卫者角色,也可以当作异常处理函数(有时候会用到)【《深度探索C++对象模型》原话】
// test23.cpp |
|
class Base { |
|
public: |
|
Base(int i) |
|
: m_i(i) |
|
{} |
|
virtual |
|
~Base() { |
|
m_i = 0; |
|
} |
|
virtual |
|
int getInt() { |
|
return m_i; |
|
} |
|
virtual |
|
void increaseInt() { |
|
m_i++; |
|
} |
|
virtual |
|
long getLong() = 0; |
|
private: |
|
int m_i; |
|
}; |
|
class Derived: public Base { |
|
public: |
|
Derived(int i, long l) |
|
: Base(i), |
|
m_l(l) |
|
{} |
|
virtual |
|
~Derived() { |
|
m_l = 0; |
|
} |
|
virtual |
|
int getInt() override { // overrid Base::getInt() |
|
return Base::getInt() + 1; |
|
} |
|
virtual |
|
long getLong() override { // overrid Base::getLong(),在Base中是一个纯虚函数 |
|
return m_l; |
|
} |
|
virtual |
|
void increaseLong() { // new virtual function |
|
++m_l; |
|
} |
|
private: |
|
long m_l; |
|
}; |
|
int main() { |
|
Derived* pd = new Derived(1, 2L); |
|
int i = pd->getInt(); |
|
pd->increaseInt(); |
|
long l = pd->getLong(); |
|
pd->increaseLong(); |
|
pd->~Derived(); |
|
delete pd; |
|
} |
另外,在这里我们可以注意到一个问题,虚表指针指向的空间,前两个表项都显示是Derived::~Derived(),也就是都是析构函数,而且地址不一样,这是怎么回事?我们看一下这两处地方的汇编代码
可以看到,第一个析构函数就是普通的析构函数它先调用了我们自己定义的析构函数,再调用了基类的析构函数Base::~Base;而第二个虚构函数则是先调用了第一个析构函数,再调用了::operator delete
(_ZdlPvm使用c++filt工具查看可知其就是operator delete(void*, unsigned long)
)。
那是不是就是当我们自己调用Derived::~Derived时调用第一个,使用delete操作符时调用的就是第二个呢?我们看到反汇编
可以看到确实是这样的。同时,我们还有一个小发现,就是当delete操作符操作的指针是nullptr时,是不会调用析构函数的,编译器真是相当费心了(在我的测试下好像是只有delete一个指向有虚析构函数的对象的指针时才会检查,否则就直接不检查调用::operator delete
)。