5

我遇到了这个:

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;

令人惊讶的是,Base::delete 内部的条件不满足 我做错了什么吗?void*还是从丢失的信息中铸造Derived*

4

3 回答 3

15

Functionoperator delete是一个原始内存释放函数。当实际对象(曾经驻留在该内存中的对象)已经被破坏时调用它。即当你进入operator delete你的对象时已经被消灭了。指针指向的内存本质上是“原始的”,它不再包含对象。试图在这个原始内存上使用任何多态功能是没有用的——它不会起作用。

用更正式的术语来说,根据语言标准,具有非平凡析构函数的对象的生命周期在其析构函数启动时结束。在您的情况下,所有析构函数都已经完成了他们的工作。对象的生命周期已经结束,同时dynamic_cast需要一个“活动”对象。

PS 形式上,dynamic_cast只要满足某些条件(参见 12.7/5),就可以在析构函数中使用,但是当所有析构函数完成时(如您的情况),dynamic_cast就不再可用。

于 2011-06-22T05:59:27.880 回答
9

一旦你的operator delete重载获得了指针,指向的对象就被销毁了(~Derived()析构函数已经被调用了)。

在它被销毁后,你再也不能把它当作一个BaseDerived对象来对待了,因为它不再是一个BaseDerived对象了。

于 2011-06-22T05:57:59.243 回答
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程序中的每个子对象)。

于 2011-06-22T07:55:17.343 回答