积极答复者
关于VC8多继承的一个问题,是否是为实现多继承对象布局的bug?

问题
-
struct _base1{};
struct _base2{};
struct _inheritance : _base1, _base2{};
int main()
{
// Ok no problem
_base1* p1 = new _inheritance;
delete p1;
// p2 now point to _base2 subobject, and the delete operator will cause error
_base2* p2 = new _inheritance;
delete p2;
return 0;
}
由于VC8实现多继承的机制,第二种情况下,p2的值会为了指向_base2类型的子对象,增加了1个byte,原因是_base2在继承列表中排列顺序在后,这也是多继承的一种实现机制,Insight C++ Object Module中也早已说明。可以通过使用/d1reportAllClassLayout编译选项观察到这一点,如下:
1>class _base1 size(1):
1> +---
1> +---
1>class _base2 size(1):
1> +---
1> +---
1>class _inheritance size(1):
1> +---
1> | +--- (base class _base1)
1> | +---
1> | +--- (base class _base2)
1> | +---
1> +---
也可以通过反汇编看到:
; this is the first case, p1 point to subobject _base1 which is just the address of _inheritance and no problem
00401000 push 1
00401002 call operator new (401048h)
00401007 push eax
00401008 call operator delete (40104Eh)
; this is the 2nd case, p2 point to subobject _base2 and ...
0040100D push 1
0040100F call operator new (401048h)
00401014 add esp,0Ch
00401017 test eax,eax
00401019 je wmain+2Ah (40102Ah)
0040101B add eax,1 ; here, for multi inheritance, the point add an offset of 1 to subobject _base2
0040101E push eax
0040101F call operator delete (40104Eh) ; and, the call to operator delete will cause error, the free failed.
因为观察到以上现象,很容易观察到是按照继承列表顺序,所以,试验了下面的情况
struct _inheritance : _base1, _base2, _base3 {};
_base3* p3 = new _inheritance;
delete p3;
果然也是这样。
多继承一直是C++实现的一个问题,所以,我猜测其他编译器也许有类似的或者其他问题,于是对GCC进行了测试,但是,GCC在这一点上没有问题。
答案
-
实际上,您的代码是有问题的。如果您希望析构函数能够正确的得到调用。请给予最顶级的类virtual的析构函数:
Code Snippetstruct _base1
{
virtual ~_base1(){ }
};
struct _base2{
virtual ~_base2(){ }
};
struct _inheritance : _base1, _base2{};
int main()
{
// Ok no problem
_base1* p1 = new _inheritance;
delete p1;
// p2 now point to _base2 subobject, and the delete operator is ok_base2* p2 = new _inheritance;
delete p2;
return 0;
}实际上对于GCC在Diamond多重继承的状态下也会有这个问题。因为对于第二个基类,将没有恰当的vptr指引析构的方向。从而产生了这个问题。这是一个Undefined Behavior
Code Snippet#include <iostream>
#include <string>
using namespace std;
class GrandParent {
public:
// Adding a virtual destructor to the grandparent on the right side
// fixes the problem
//virtual ~GrandParent() {}
// potential workaround:
// adding a pure virtual function to handle deleting the object fixes the problem
virtual void deleteMe()=0;
};
class LeftParent: public GrandParent {
public:
// a virtual destructor in the left parent doesn't help
//virtual ~LeftParent() {}
};
class RightParent : public GrandParent {
public:
// a virtual destructor on the right parent also fixes the problem
//virtual ~RightParent() {}
};
class Child: public LeftParent, public RightParent {
public:
void deleteMe() {
delete this;
}
};
int main(int argc, char **argv) {
// Declaring object to be a pointer to RightParent will cause a
// segfault when it is deleted. Declaring it a pointer to LeftParent
// or Child produces the desired (non-segfaulting) behavior.
RightParent *object = new Child();
//LeftParent *object = new Child();
//Child *object = new Child();
//cout << __FILE__ << ": trying to delete object " << object << ": " << endl;
// comment out the 'delete object' line and uncomment this next line
// to try the virtual function workaround. It works, but is clunky.
//object->deleteMe();
delete object;
//cout << __FILE__ << ": success! " << endl;
return 0;
}
全部回复
-
实际上,您的代码是有问题的。如果您希望析构函数能够正确的得到调用。请给予最顶级的类virtual的析构函数:
Code Snippetstruct _base1
{
virtual ~_base1(){ }
};
struct _base2{
virtual ~_base2(){ }
};
struct _inheritance : _base1, _base2{};
int main()
{
// Ok no problem
_base1* p1 = new _inheritance;
delete p1;
// p2 now point to _base2 subobject, and the delete operator is ok_base2* p2 = new _inheritance;
delete p2;
return 0;
}实际上对于GCC在Diamond多重继承的状态下也会有这个问题。因为对于第二个基类,将没有恰当的vptr指引析构的方向。从而产生了这个问题。这是一个Undefined Behavior
Code Snippet#include <iostream>
#include <string>
using namespace std;
class GrandParent {
public:
// Adding a virtual destructor to the grandparent on the right side
// fixes the problem
//virtual ~GrandParent() {}
// potential workaround:
// adding a pure virtual function to handle deleting the object fixes the problem
virtual void deleteMe()=0;
};
class LeftParent: public GrandParent {
public:
// a virtual destructor in the left parent doesn't help
//virtual ~LeftParent() {}
};
class RightParent : public GrandParent {
public:
// a virtual destructor on the right parent also fixes the problem
//virtual ~RightParent() {}
};
class Child: public LeftParent, public RightParent {
public:
void deleteMe() {
delete this;
}
};
int main(int argc, char **argv) {
// Declaring object to be a pointer to RightParent will cause a
// segfault when it is deleted. Declaring it a pointer to LeftParent
// or Child produces the desired (non-segfaulting) behavior.
RightParent *object = new Child();
//LeftParent *object = new Child();
//Child *object = new Child();
//cout << __FILE__ << ": trying to delete object " << object << ": " << endl;
// comment out the 'delete object' line and uncomment this next line
// to try the virtual function workaround. It works, but is clunky.
//object->deleteMe();
delete object;
//cout << __FILE__ << ": success! " << endl;
return 0;
} -
lxconan 写: 这个还是第一次听说。请问您能够给出一些例子以证明这一点吗?实际上这对于某些情况非常重要。
这点并不重要,因为标准并没有对此做任何规定。不要依赖于编译器的特定行为。
Code Snippet#include <iostream>
using namespace std;class A
{
public:
//virtual void f() {}
int a;
};
class B
{
public:
virtual void f() {}
int b;
};class C:public A,public B
{
virtual void f() {}
};int main()
{
C c;
cout<<static_cast<A*>(&c)<<endl;
cout<<static_cast<B*>(&c)<<endl;
}B在A的前面,如果你把A里的注释去掉,A就会在B的前面