【问题标题】:What does C++03 12.4/12 say about calling a base class destructor explicitly through the pointer?C++03 12.4/12 对通过指针显式调用基类析构函数有什么看法?
【发布时间】:2013-12-31 21:08:41
【问题描述】:

当显式调用析构函数时,根据 C++03 12.4/12

如果对象不是析构函数的类类型并且不是从析构函数的类类型派生的类,则程序具有未定义的行为

所以我有这个代码:

class Base {};
class Derived : public Base {};

char memory[100];
new(memory) Derived();
Base* ptr = (Base*)memory;
ptr->~Base();

这里的对象是Derived 类型,而“析构函数的类类型”是Base,所以根据标准的措辞,UB 是没有根据的。

那么上面的代码是否根据标准产生了 UB?

【问题讨论】:

  • 派生通常会提供一些成员吗?
  • @itwasntpete:嗯,不一定。例如,您可以制作一个新的异常类,无需添加任何新成员即可单独捕获。
  • 我有一种预感,这是 UB,因为 12.4/12 允许使用 derivedPtr->~Base() 形式以支持虚拟析构函数。不过,这里没有可用的 C++03。

标签: c++ destructor undefined-behavior


【解决方案1】:

正确,没有未定义的行为。

相比之下,根据所涉及的类型,在这种情况下可能存在 UB:

Base *ptr = new Derived();
delete ptr;

原因是对于某些类型,实现可能已应用调整以从Derived* 变为Base*。因此,如果没有指向完整对象的指针,就无法正确释放内存分配。虚拟析构函数确保Base 子对象为实现提供足够的信息来恢复它(虚拟调用机制必须能够恢复Derived* 指针,以便将其作为this 传递)。

但是在您的示例中,内存没有被释放,因此没有动力将其设为 UB。当然,这仍然是个坏主意,因为从概念上讲,Derived 对象处于损坏状态。你甚至没有合法的方式打电话给~Derived。在您的示例中,虽然这两种类型都可以简单地破坏,但没有需要调用任何一种的析构函数。

【讨论】:

  • Emmm... 调整? Derived 可以很容易地在另一个堆上实现 operator new(),即使没有任何调整也会导致可怕的后果。
  • @sharptooth:是的,好点。如果 newdelete 没有为这些类型重载,那么出于我的原因它仍然是 UB。
  • 其实new-delete的例子还是UB。你的例子和我的例子只是可以打破的例子。
  • @sharptooth:是的,我提出它的原因是因为它有成为 UB 的理由。在不知道这个理由的情况下,人们可能会认为您的示例也应该是 UB,因为它表面上类似于他们被告知是 UB 的案例,并且通常是由任何可靠的书或老师。 Ofc 我只提供了部分理由,在某种程度上破坏了这个计划......
【解决方案2】:

这不是 UB,因为这两个类都有微不足道的析构函数,因此调用析构函数与 not 调用析构函数的效果相同(并且 not 调用析构函数肯定是不是UB)。

【讨论】:

  • 这是否也适用于Base* base = new Derived(); delete base; 案例?
  • @sharptooth:我什至会说是;事实上,我认为Base base; static_cast<Derived *>(&base)->~Derived(); 应该也能正常工作(听起来很荒谬),因为它们都有微不足道的析构函数,这意味着它们的析构函数一定没有效果......
  • 问题中引用的标准文本对于琐碎的析构函数没有例外。这个答案不是关于未定义的行为,而是关于代码的可能后果。
【解决方案3】:

如果我错了,请纠正我,我认为没有未定义的行为,但出于人性(或可维护性)的考虑仍应避免。但是,考虑Derived 正在创建某种成员,例如一个共享指针,(即使对于异常也不是不典型的:)。我在我的机器和codepad 上尝试了这段代码:

class Base {
public:
  boost::shared_ptr<int> x;
};
class Derived : public Base {
public:
  boost::shared_ptr<int> y;
};


int main(int argc, char *argv[]) {
  boost::shared_ptr<int> xx(new int(0xff));
  boost::shared_ptr<int> yy(new int(0xaa));

  int memory[100];
  for(int i=0; i<100; i++)
    memory[i] = 0;
  Derived* foo = new(memory) Derived();
  foo->x = xx;
  foo->y = yy;
  (*foo->y)--;
  Base* ptr = (Base*)foo;

  ptr->~Base();

  Derived* bar = new(memory) Derived();

  bar->x = xx;
  bar->y = yy;

  foo->~Derived();

  return 0;
}

这里是 shared_ptr yy 没有发布,没有人能保证永远不会忘记 Derived 不应该提供任何东西。

【讨论】:

  • 没有发布的东西不代表UB。
  • 在这种情况下,我认为 UB 正在构建一个对象,该对象由一个未被破坏的非平凡可破坏对象占用的内存。所以我认为 UB 是对new(memory) Derived() 的第二次调用,它在y 成员上乱涂乱画。但即使我错了而且那不是 UB,那么 bar-&gt;y 是 null 所以取消引用它是。 [那句话在键盘代码上,我只是注意到这里的代码不同。]
猜你喜欢
  • 2014-02-17
  • 2023-03-31
  • 1970-01-01
  • 2018-11-05
  • 1970-01-01
  • 2012-07-22
  • 2016-10-06
  • 2010-12-24
  • 1970-01-01
相关资源
最近更新 更多