C++中的虚函数的内部实现机制到底是怎样的呢?
鉴于涉及到的内容有点多,我将分三篇文章来介绍。
第一篇:对象内存模型浅析,这里我将对对象的内存模型进行简单的实验和总结。
第二篇:继承对象的构造和析构浅析,这里我将对存在继承关系的对象的构造和析构进行简单的实验和总结。
第三篇:虚函数的内部机制浅析,这里我将对虚函数内部的实现机制进行实验总结。
我使用的编译器是VS2008,有不足或者不准确的地方,欢迎大家拍砖(我个人非常迫切的希望得到大家的指正),我会及时修正相关内容。
在虚函数详解第一篇中,我简单的介绍了C++对象内存模型。我们了解到派生类对象是由基类部分和派生部分构成的,那么该派生类对象是如何被构造和析构的呢?
#include <tchar.h>
#include <iostream>
using namespace std;
class Person
{
public:
Person()
{
cout << _T("基类的构造函数被调用") << endl;
}
~Person()
{
cout << _T("基类的析构函数被调用") << endl;
}
};
class Man : public Person
{
public:
Man()
{
cout << _T("派生类的构造函数被调用") << endl;
}
~Man()
{
cout << _T("派生类的析构函数被调用") << endl;
}
};
int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
{
Man Mike;
return 1;
}
上述代码的执行结果如下:
我们可以看到:构造一个派生类对象的时候,先调用基类的构造函数,再调用派生类的构造函数,析构一个派生类对象的时候,先调用派生类的析构函数,再调用基类的析构函数。
上述内容讲述的是普通派生类的构造和析构过程,对于具有虚函数的派生类的构造和析构过程是怎样的呢?
#include <tchar.h>
#include <iostream>
using namespace std;
class Person
{
public:
Person()
{
cout << _T("基类的构造函数被调用") << endl;
}
virtual void Height()
{
cout << _T("人类具有身高属性") << endl;
}
virtual ~Person()
{
cout << _T("基类的析构函数被调用") << endl;
}
};
class Man : public Person
{
public:
Man()
{
cout << _T("派生类的构造函数被调用") << endl;
}
virtual void Height()
{
cout << _T("男人具有身高属性") << endl;
}
virtual ~Man()
{
cout << _T("派生类的析构函数被调用") << endl;
}
private:
double m_dHeight;
double m_dWeight;
};
int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
{
Person* pPersonObj = new Man;
delete pPersonObj;
return 1;
}
上述代码的执行结果如下:
大家可能注意到了,上述代码中基类和派生类的析构函数都采用虚析构函数,而在_tmain函数中的调用方式也采用了Person* pPersonObj = new Man这种多态调用方式。当delete pPersonObj被执行来释放派生类对象的时候,实际上调用的是派生类对象的虚析构函数,而派生类对象的虚析构函数会调用基类的析构函数,这样就能将派生类对象完美的析构,如果这里不采用虚析构函数,会是什么结果呢?
#include <tchar.h>
#include <iostream>
using namespace std;
class Person
{
public:
Person()
{
cout << _T("基类的构造函数被调用") << endl;
}
virtual void Height()
{
cout << _T("人类具有身高属性") << endl;
}
~Person()
{
cout << _T("基类的析构函数被调用") << endl;
}
};
class Man : public Person
{
public:
Man()
{
cout << _T("派生类的构造函数被调用") << endl;
}
virtual void Height()
{
cout << _T("男人具有身高属性") << endl;
}
virtual ~Man()
{
cout << _T("派生类的析构函数被调用") << endl;
}
private:
double m_dHeight;
double m_dWeight;
};
int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
{
Person* pPersonObj = new Man;
delete pPersonObj;
return 1;
}
上述代码执行结果如下:
我们可以看到,当delete pPersonObj被执行的时候,只调用了基类的析构函数,并没有调用派生类的析构函数,所以这个对象的派生部分的内存并没有被释放,从而造成内存泄露。
所以:当基类中包含有虚函数的时候,析构函数一定要写成虚析构函数,否则会造成内存泄露。
为什么一定要这么做呢?我们在第三篇的内容里寻找答案。