单继承
派生类的定义
class Derive : public Base
{
public:
Derive(int d) :Base(1000), DeriveI(d) {};
//overwrite父类虚函数
virtual void print(void) { cout << "Drive::print_overwrite"; }
// Derive声明的新的虚函数
virtual void Drive_print() { cout << "Drive::Drive_print_newwrite"; }
virtual ~Derive() {}
private:
int DeriveI;
};
继承类图
简单对象模型
简单对象模型的缺点就是因间接性导致的空间存取时间上的额外负担,优点则是类的大小是固定的,基类的改动不会影响子类对象的大小。
表格驱动对象模型
在表格驱动对象模型中,我们可以为子类对象增加第三个指针:基类指针(bptr),基类指针指向指向一个基类表(base class table),同样的,由于间接性导致了空间和存取时间上的额外负担,优点则是无须改变子类对象本身就可以更改基类。
c++对象模型
在C++对象模型中,对于一般继承(这个一般是相对于虚拟继承而言),若派生类重写(overwrite)了基类的虚函数,则派生类虚函数将覆盖虚表中对应的基类虚函数;若子类并没有重写overwrite基类虚函数,而是声明了自己新的虚函数,则该虚函数地址将扩充到虚函数表最后(在vs中无法通过监视看到扩充的结果,不过我们通过取地址的方法可以做到,派生类新的虚函数确实在基类的虚函数表末端)。虚函数表以0x0000000结束,类似字符串以 ’ \0 ’ 结束。
图中虚表位置的析构函数和重写后的print函数位置颠倒了
代码演示
typedef void(*Fun)(void);
int main()
{
Base b(1000);
//[0]
cout << "[0]Base::vptr";
cout << "\t地址:" << (int*)(&b) << endl;
Fun funb0 = (Fun) * ((int*)*((int*)(&b)));
//funb0();
//析构函数无法通过地址调用,故手动输出
cout << " [0]" << "Base::~Base";
cout << "\t地址:\t" << *((int*)*((int*)(&b))) << endl;
cout << " [1]";
Fun funb1 = (Fun) * ((int*)*((int*)(&b)) + 1);
funb1();
cout << "\t地址:\t" << *((int*)*((int*)(&b)) + 1) << endl;
cout << "[1]Base::baseI=" << *(int*)((int*)(&b) + 1);
cout << "\t地址:" << (int*)(&b) + 1;
cout << endl;
cout << "---------------------------" << endl;
Derive d(2000);
//[0]
cout << "[0]Base::vptr";
cout << "\t地址:" << (int*)(&d) << endl;
//vprt[0]
Fun fun0 = (Fun) * ((int*)*((int*)(&d)));
//fun0();
//vprt[1]析构函数无法通过地址调用,故手动输出
cout << " [0]" << "Derive::~Derive";
cout << "\t地址:\t" << *((int*)*((int*)(&d))) << endl;
cout << " [1]";
Fun fun1 = (Fun) * ((int*)*((int*)(&d)) + 1);
fun1();
cout << "\t地址:\t" << *((int*)*((int*)(&d)) + 1) << endl;
//vprt[2]
cout << " [2]";
Fun fun2 = (Fun) * ((int*)*((int*)(&d)) + 2);
fun2();
cout << "\t地址:\t" << *((int*)*((int*)(&d)) + 2) << endl;
//[1]
cout << "[1]Base::baseI=" << *(int*)((int*)(&d) + 1);
cout << "\t地址:" << (int*)(&d) + 1;
cout << endl;
//[2]
cout << "[2]Derive::DeriveI=" << *(int*)((int*)(&d) + 2);
cout << "\t地址:" << (int*)(&d) + 2;
cout << endl;
getchar();
}
在vs中,此处派生类对象的虚表中并没有新的虚函数,但是可以通过地址访问到新写的虚函数。
运行结果与对象模型中的描述相符合。