【问题标题】:Does dynamic_cast work inside overloaded operator delete?dynamic_cast 在重载的运算符删除中工作吗?
【发布时间】:2011-06-22 05:55:36
【问题描述】:

我遇到了这个:

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

【问题讨论】:

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


【解决方案1】:

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

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

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

【讨论】:

  • "[T]一个具有非平凡析构函数的对象的生命周期在其析构函数启动后结束"?那不应该是析构函数完成的时候。 derived 类型的对象将一直存在,直到到达 ~derived 的右大括号,不是吗?
  • 哦,根据标准,你是对的:§3.8/1 类型 T 的对象的生命周期在以下情况下结束: -- 如果 T 是具有非平凡析构函数的类类型(12.4),析构函数调用开始,或者[...]
  • @David Rodriguez 物体的死亡不是瞬间的。进入析构函数后,从外部看,该对象已正式不复存在。但是,它的行为或多或少类似于析构函数中的对象;可以将其视为处于困境中。 (当然,一旦析构函数终止,从任何角度来看,都不再有对象了。)
【解决方案2】:

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

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

【讨论】:

    【解决方案3】:

    正如其他两个答案已经提到的,对象的类型随着析构函数的执行而改变。一旦析构函数完成,该类型的对象就不再存在,只有它的基础子对象存在(直到它们的析构函数完成)。

    这个答案的原因是提出一个有趣的实验,这段代码的输出会是什么? (哦,好吧,三个答案都已经告诉你了,但实验本身很有趣):

    #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 的构造应已开始且其销毁不应完成,否则指针值的计算(或访问成员值)导致未定义的行为。

    在调用print_type 时转换为base&amp; 是有效的,因为它们是在每个 对象的构造开始之后、每个 的销毁之前执行的对象已完成(每个指程序中most_derived的每个子对象)。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-11-18
      • 1970-01-01
      • 2015-05-25
      • 2011-10-10
      • 1970-01-01
      • 2014-09-27
      • 1970-01-01
      • 2015-06-26
      相关资源
      最近更新 更多