dynamic_cast是否在重载运算符内部工作?

时间:2011-06-22 05:55:36

标签: c++ operator-overloading dynamic-cast

我遇到了这个:

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*的信息?

3 个答案:

答案 0 :(得分:15)

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

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

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

答案 1 :(得分:9)

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

由于它不再是BaseDerived对象,因此在销毁它之后,您再也不能将其视为BaseDerived对象了。

答案 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的每个子对象)。