我不明白一件事。例如,我声明A类和B类,它是A:
的子代class A {
public:
int a;
}
class B : public A {
public:
int b;
}
显然,如果我创建A或B的实例,它们在内存中的大小可以由类型确定。
A instanceA; // size of this will probably be the size of int (property a)
B instanceB; // size of this will probably be twice the size of int (properties a and b)
但是如果我创建动态实例然后在以后释放它们呢?
A * instanceAPointer = new A();
A * instanceBPointer = new B();
这些是不同类的实例,但是程序会将它们视为A类的实例。使用它们时很好但是如何释放它们呢?要释放分配的内存,程序必须知道要释放的内存大小,对吧?
所以,如果我写
delete instanceAPointer;
delete isntanceBPointer;
程序如何知道,有多少内存,从每个指针指向的地址开始,它应该是空闲的?因为很明显,对象具有不同的大小,但程序认为它们是A类型。
由于
答案 0 :(得分:6)
我假设您知道如何delete works。
关于delete
如何知道如何清理继承的实例。这就是你在继承上下文中使用virtual
destructor的原因,否则你将会有未定义的行为。基本上,析构函数与每个其他virtual
函数一样,都是通过vtable
调用的。
还要记得:C ++编译器隐式破坏析构函数中的父类
class A {
public:
int a;
virtual ~A(){}
}
class B : public A {
public:
int b;
~B() { /* The compiler will call ~A() at the very end of this scope */ }
}
这就是为什么这会起作用;
A* a = new B();
delete a;
通过vtable
,析构函数~B()
将由delete
调用。由于编译器隐式地在派生类中插入基类的析构函数调用,因此将A
中调用~B()
的析构函数。
答案 1 :(得分:3)
如果通过指向基础子对象的指针删除对象并且子对象的类没有虚拟析构函数,则行为未定义。
另一方面,如果它确实有一个虚拟析构函数,那么虚拟调度机制负责为正确的地址释放正确数量的内存(即完整的,派生最多的对象)。
通过将dynamic_cast<void*>
应用于任何适当的基础子对象指针,您可以自己发现最派生对象的地址。 (另见this question。)
答案 2 :(得分:2)
要释放已分配的内存,程序必须知道要释放的内存大小,对吧?
如果您考虑使用C库malloc
和free
,您将看到在调用{{1}时无需指定要释放的内存量},即使在这种情况下,free
提供了free
,因此无法推断它。相反,分配库通常要么记录,要么可以推断出所提供的内存足够多,这样只有指针就足以进行重新分配。
C ++释放例程仍然如此:如果基类提供了自己的void*
和,则基类析构函数为static void operator delete(void*, std::size_t)
,那么它将被传递给动态类型的大小。默认情况下,重新分配最终会在virtual
结束,但不会给出任何大小:分配例程本身必须足够熟悉才能运行。
分配例程可以有多种方式,包括:
存储分配的大小
从相同大小的块池中分配类似大小的对象,这样任何指向该池的指针都隐含地与该块大小相关