C++多重继承
今天在考虑一个C++实现的时候需要用到多重继承,突然发觉这么多年来基本都没怎么用过多重继承
只是了解多重继承可以用,不过很容易出问题,尤其是基类是从相同的类派生出来的。
今天我需要用到的没有这么麻烦,不过我还是对于多重继承的内存释放感到困惑。
大家应该都知道设计C++类的时候,如果这个类需要被继承,建议把析构函数声明为virtual的。
之前对于这一点的理解是:
基类成员的内存释放一般是在基类析构函数里面完成的,如果基类析构函数不是虚函数,派生类析构的时候不会调用基类的,就会导致内存泄露。
下面是随手写的一个多重继承测试用例
{
public:
~Interface1()
{
printf("~Interface1\n");
};
virtual void Func1() = 0;
protected:
int mData1;
};
class Interface2
{
public:
~Interface2()
{
printf("~Interface2\n");
};
virtual void Func2() = 0;
protected:
int mData2;
};
class Base
{
public:
~Base()
{
printf("~Base\n");
};
void Func3()
{
printf("Func3\n");
}
protected:
int mData;
};
class Derived : public Base, public Interface1, public Interface2
{
public:
~Derived()
{
printf("~Derived\n");
};
void Func1()
{
printf("Func1\n");
}
void Func2()
{
printf("Func2\n");
}
};
////////////////////////////////////////////
// Test case
////////////////////////////////////////////
Derived* d = new Derived();
Base* b = d;
Interface1* i1 = d;
Interface2* i2 = d;
i1->Func1();
i2->Func2();
b->Func3();
delete i2;
问题来了,delete i2;这一行会导致Assert(Debug版)
这是为什么呢?
先让我们在watch window里面看看这些变量
i2 0x004e99c8 Interface2 *
b 0x004e99d0 Base *
d 0x004e99c0 Derived *
Wow,地址竟然都不一样,想想也对,因为虚表的原因。具体的可以参考:这里
当我把所有析构函数改成virtual的以后,delete就没有问题了。
这中间发生了什么事情呢?因为i2的地址和我们分配的地址明显是不一样,直接free这块内存的话,当然会出错
查看正确的析构代码的汇编代码发现窍门在这里
i2的Interface2虚表中的析构函数指向了下面这个地址
004117D0 sub ecx,10h
004117D3 jmp Interface2::`scalar deleting destructor' (4113C5h)
==>
004113C5 jmp Derived::`scalar deleting destructor' (411860h)
注意sub ecx, 10h,经过这个变幻以后得到的地址就是我们前面真正的内存地址。
也就是说在每个基类的虚表中指向的析构函数其实并不是Derived的析构函数地址,而是一个编译器自动生成的中间代码地址,这段代码的功能就是把这个基类的实例内存地址(this指针)转换成内存分配时的真正地址(也就是当时实例化的类型),然后再调用实际类型的析构函数(Derived::~Derived())。