我想知道为什么我在这里的一个案例中delete
部分会有例外,但在另一个案例中却没有。
#include <iostream>
using namespace std;
class A
{
public:
~A() { cout << "A dtor" << endl; }
};
class B : public A
{
public:
int x;
~B() { cout << "B dtor" << endl; }
};
A* f() { return new B; }
int _tmain(int argc, _TCHAR* argv[])
{
cout << sizeof(B) << " " << sizeof(A) << endl;
A* bptr= f();
delete bptr;
}
此处输出为4 1 .. A dtor
,因为A有1个字节用于标识,B有4个用于int x
。
#include <iostream>
using namespace std;
class A
{
public:
~A() { cout << "A dtor" << endl; }
};
class B : public A
{
public:
virtual ~B() { cout << "B dtor" << endl; }
};
A* f() { return new B; }
int _tmain(int argc, _TCHAR* argv[])
{
cout << sizeof(B) << " " << sizeof(A) << endl;
A* bptr= f();
delete bptr;
}
这里的输出是4 1 .. A dtor
,因为A有1个字节用于标识,B有4个,因为它的虚拟析构函数需要vptr
。
但是调试断言在delete
调用(_BLOCK_TYPE_IS_VALID
)中失败。
我正在使用Visual Studio 2010 SP1Rel运行Windows 7。
答案 0 :(得分:3)
请参阅this post
快速摘要:
当然,解决方案是将基类(A
)dtor
声明为virtual
,因此始终会调用B
的{{1}}
编辑:对于第一种情况,这是标准所说的内容:
§5.3在第一个备选(删除对象)中,如果要删除的对象的静态类型与其不同 动态类型,静态类型应该是要删除的对象的动态类型的基类 static类型应具有虚拟析构函数或行为未定义。在第二种选择中(删除 array)如果要删除的对象的动态类型与其静态类型不同,则行为未定义。
因此,这两种情况都将我们引向了未定义行为的领域,这当然不同于一种实现方式。但有理由相信,对于大多数实现来说,第一种情况比第二种情况更容易处理或至少更容易考虑,而第二种情况只是一种深奥的反模式。
答案 1 :(得分:1)
正如其他人所指出的,您正在删除静态类型与其动态类型不同的对象,并且由于静态类型没有虚拟析构函数,因此您将获得未定义的行为。这包括有时工作的行为,有时候你看不到工作。但是,我认为您有兴趣更深入地了解特定编译器的情况。
类A
根本没有成员,所以它的数据布局最终看起来像这样:
struct A {
};
由于类B
派生自类A
,类A
将嵌入到B中。当类B
没有虚函数时,布局最终如下所示:
struct B {
A __a_part;
int x;
};
编译器可以通过取B*
的地址将A*
转换为__a_part
,就像编译器有这样的函数一样:
A * convertToAPointer(B * bp){return&amp; bp-&gt; __ a_part; }
由于__a_part
是B
的第一个成员,B*
和A*
指向同一地址。
这样的代码:
A* bptr = new B;
delete bptr;
有效地做这样的事情:
// Allocate a new B
void* vp1 = allocateMemory(sizeof(B));
B* bp = static_cast<B*>(vp1);
bp->B(); // assume for a second that this was a legal way to construct
// Convert the B* to an A*
A* bptr = &bp->__a_part;
// Deallocate the A*
void* vp2 = ap;
deallocateMemory(vp2);
在这种情况下,vp2
和vp1
是相同的。系统正在分配和释放相同的内存地址,因此程序运行时没有错误。
当类B
具有虚拟成员函数时(本例中为析构函数)。编译器添加了一个虚拟表指针,因此B类最终看起来像这样:
struct B {
B_vtable* __vptr;
A __a_part;
};
此处的问题是__a_part
不再是第一个成员,convertToAPointer
操作现在将更改指针的地址,因此vp2
和vp1
否更长的点指向同一地址。由于释放的内存位置与分配的内存位置不同,因此会出现错误。