【问题标题】:The state of an object between a call to ~Derived() and ~Base()调用 ~Derived() 和 ~Base() 之间的对象状态
【发布时间】:2012-06-29 10:12:09
【问题描述】:

问题

C++ 标准对对象在时间中的状态有什么保证 在派生类的析构函数执行之后,但在基类的析构函数执行之前? (这是调用派生类的子对象的析构函数的时间。)

示例

#include <string>
struct Base;

struct Member {
  Member(Base *b);
  ~Member();
  Base *b_;
};

struct Base {
  virtual void f() {}
  virtual ~Base() {}
};

struct Derived : Base {
  Derived() : m(this) {}
  virtual ~Derived() {}  
  virtual void f() {}
  std::string s; 
  Member m;
};

Member::Member(Base *b) : b_(b) {}
Member::~Member() {
  // At this point, ~Derived has finished -- can we use b_ as a 
  // Derived* object (i.e. call Derived::f or access Derived::s)?
  b_->f();
}

int main() {
  Base *bd = new Derived;
  delete bd;
}

在此示例中,Member 对象有一个指向拥有它的 Derived 对象的指针,并且它尝试访问该 Derived 对象,因为它已被破坏......即使 Derived 的析构函数已经结束了。

如果某个子对象在~Derived() 执行之后、~Base() 执行之前调用虚函数,会调用*bd 的哪个版本的虚函数? *bd 处于那种状态时访问它是否合法?

【问题讨论】:

  • 我无法背诵规范中涵盖此内容的部分,但请问问自己;从实际的角度来看,它完全是个好主意吗?可能不会。
  • +1 表示完整的安全带。

标签: c++ destructor object-lifetime


【解决方案1】:

对我来说,从 [12.4] 开始,它显然是不合法的:

一旦为一个对象调用析构函数,该对象就不再 存在; 如果为某个对象调用析构函数,则行为未定义 生命周期已结束的对象 (3.8)。 [示例:...]

尽管没有不再存在的定义,我认为,我可以说引用一个不再存在的对象会导致未定义的行为强>。

【讨论】:

  • 这句话看起来很有希望,但它看起来(对我来说)更像是说为同一个对象运行两次析构函数是 UB。
  • @NateKohl 我已将引用的相关部分加粗,其余部分可以省略。
  • 至于“不再存在”,[3.8] 说:“类型 T 的对象的生命周期在以下情况下结束:- 如果 T 是具有非平凡析构函数的类类型 (12.4) ,析构函数调用开始,或者——对象占用的存储空间被重用或释放”
  • 在析构函数启动后,对象的生命周期可能已经结束,但你仍然可以与对象进行交互——即析构函数的主体可以做各种事情。这让我对“对象生命周期”的概念有点不清楚......
  • 我同意,C++11 标准中没有其他地方可以澄清这个问题。
【解决方案2】:

来自 n3290 [class.cdtor]

12.7 构造和销毁 1 对于具有非平凡构造函数的对象,在构造函数开始执行之前引用对象的任何非静态成员或基类会导致未定义的行为。 对于具有非平凡析构函数的对象,在析构函数完成执行后引用该对象的任何非静态成员或基类会导致未定义的行为。

【讨论】:

  • 这可能是最接近的答案,但是关于析构函数何时完成执行仍然有点模糊:12.4 说“在执行析构函数的主体之后......类X 调用的析构函数X 的直接非变体非静态数据成员的析构函数...”,也许暗示析构函数在完成对其成员的析构之前不会完成?
  • 不这么认为。我认为第 2.7 段很清楚。在析构函数执行后,调用其他析构函数(2.4),但此时我们处于未定义的行为领域(2.7)。根据定义,对象的生命周期在析构函数启动时结束,但在 dtor 执行期间,引用成员是有效的。
  • @FernandoPelliccioni "根据定义,对象的生命周期在析构函数启动时结束" 那么“生命周期”不是一个有用的概念。
  • @curiousguy 我不明白你的意思。你能详细说明一下吗?
  • @FernandoPelliccioni 如果生命周期意味着“直到析构函数开始”,那么它是一个有用的概念吗?对象的什么属性只有在析构函数启动之前才为真?
【解决方案3】:

在您调用b_-&gt;f() 的那一刻,Derived 正在被破坏,但Base 尚未被破坏。不过,您仍处于不确定状态,因为您要拨打的是 Derived::f()

编辑:

可以调用成员函数,包括虚函数(10.3) 在建造或销毁期间 (12.6.2)。当一个虚函数 从构造函数直接或间接调用(包括从 数据成员的 mem-initializer) 或析构函数,以及 调用适用的对象是正在构建的对象或 破坏,调用的函数是构造函数中定义的函数 或析构函数自己的类或其基类之一,但不是函数 在派生自构造函数或析构函数的类中覆盖它 类,或在最常见的其他基类之一中覆盖它 派生对象 (1.8)。如果虚函数调用使用显式 类成员访问(5.2.5)和对象表达式是指 正在构造或销毁的对象,但其类型既不是 构造函数或析构函数自己的类或其基类之一,结果 调用未定义。

C++0x working draft Sec 12.7 par 4

【讨论】:

  • 所以我的问题真的是:我们对这种不确定状态了解多少?成员以相反的构造顺序被破坏,所以字符串成员应该仍然存在,对吧?我们知道 vtable 指针什么时候会更新吗?
  • 嗯...这句话看起来像是在谈论在析构函数调用期间发生的事情,但不一定是在调用之后发生的事情。
  • @NateKohl:我是否正确,您是在询问,如果某个子对象在~Derived() 执行之后但在~Base() 执行之前调用虚函数,那么将调用对象虚函数的哪个版本(这是Derived 的子对象的析构函数被调用的时间)?
  • 是的;更一般地说,当Derived 对象处于该状态时,我们还能假设它还有什么(如果有的话)?
  • @NateKohl:我已经完全重写了你的问题来解决这个问题。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-07-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多