我遇到了这个:
struct Base {
void* operator new (size_t);
void operator delete (void*);
virtual ~Base () {} // <--- polymorphic
};
struct Derived : Base {};
void Base::operator delete (void *p)
{
Base *pB = static_cast<Base*>(p);
if(dynamic_cast<Derived*>(pB) != 0)
{ /* ... NOT reaching here ? ... */ }
free(p);
}
如果我们这样做,
Base *p = new Derived;
delete p;
令人惊讶的是,condition inside the Base::delete is not satisfied
我做错了吗?或者从void*
投射会丢失Derived*
的信息?
答案 0 :(得分:15)
函数operator delete
是原始内存释放函数。当实际对象(以前驻留在该内存中)已经被破坏时调用它。即当你进入operator delete
时,你的物体已经被消灭了。指针指向的内存基本上是“原始”,它不再包含一个对象。试图在这个原始内存上使用任何多态功能是没用的 - 它不起作用。
在更正式的术语中,根据语言标准,具有非平凡析构函数的对象的生命周期一旦其析构函数启动就结束。在你的情况下,所有析构函数已经完成了他们的工作。对象的生命周期已经结束,而dynamic_cast
需要一个“实时”对象。
P.S。在形式上,只要满足某些条件(见12.7 / 5),允许在析构函数中使用dynamic_cast
,但是当所有析构函数完成时(如您的情况),dynamic_cast
不再是可用的。
答案 1 :(得分:9)
一旦你的operator delete
重载获得指针,指向的对象就被破坏了(已经调用了~Derived()
析构函数)。
由于它不再是Base
或Derived
对象,因此在销毁它之后,您再也不能将其视为Base
或Derived
对象了。
答案 2 :(得分:2)
正如其他两个答案所提到的,对象的类型随着析构函数的执行而改变。一旦析构函数完成,该类型的对象就不再存在,只有它的基础子对象存在(直到它们的析构函数完成)。
这个答案的原因是提出一个有趣的实验,这段代码的输出是什么? (哦,好吧,这三个答案已经告诉过你了,但实验本身很有趣):
#include <iostream>
struct base {
static void print_type( base const & b ) { // [1]
std::cout << b.type() << std::endl;
}
virtual std::string type() const { // [2]
return "base";
}
virtual ~base() { print_type( *this ); }
base() { print_type( *this ); }
};
struct derived : base {
std::string type() const {
return "derived";
}
~derived() { print_type( *this ); }
derived() { print_type( *this ); }
};
struct most_derived : derived {
std::string type() const {
return "most_derived";
}
~most_derived() { print_type( *this ); }
most_derived() { print_type( *this ); }
};
int main() {
most_derived md;
base::print_type( md );
}
注意:
为了获得额外的乐趣,还会在构造函数中添加对print_type
的调用。该函数用作在该特定时间点验证对象的动态类型。函数print_type
(可以是一个独立的函数,并在不同的转换单元中实现 - 以避免编译器在其中看到)。在编译函数时,编译器无法知道它是从构造函数,析构函数内部还是在任何函数之外调用,因此生成的代码必须使用动态调度机制,并将被调度到每个时间点的最终覆盖。
关于代码的有效性由§12.7/ 2保证:
显式或隐式地将引用类X的对象的指针(左值)转换为指向X的直接或间接基类B的指针(引用),构造X及其所有直接构造或者直接或间接从B派生的间接基础应该已经开始,并且这些类别的销毁不应该完成,否则转换会导致不确定的行为。要形成指向对象obj的直接非静态成员(或访问其值)的指针,obj的构造应该已经开始并且它的销毁不应该已经完成,否则计算指针值(或访问成员值)导致未定义的行为。
base&
调用print_type
的转换有效,因为它们是在每个对象构建完成后执行的,并且在每个的销毁之前执行对象已完成(每个指的是程序中most_derived
的每个子对象)。