【问题标题】:Does destroying and recreating an object make all pointers to this object invalid?销毁和重新创建对象是否会使指向该对象的所有指针无效?
【发布时间】:2012-09-12 20:08:29
【问题描述】:

这是this question 的后续行动。假设我有这个代码:

class Class {
    public virtual method()
    {
        this->~Class();
        new( this ) Class();
    }
};

Class* object = new Class();
object->method();
delete object;

这是this answer 建议的简化版本。

现在,一旦从method() 中调用析构函数,对象生命周期就会结束,调用代码中的指针变量object 将变为无效。然后在同一位置创建新对象。

这是否使指向调用中对象的指针再次有效?

【问题讨论】:

  • 指针失效不会完全违背放置新的目的吗?
  • 为什么这个函数是虚拟的?有点吓人。
  • 另外,如果Class 的构造函数抛出异常,你就陷入了道德困境。 (并且不要称你的班级为“班级”......)
  • @Luc Touraille:嗯,我不知道在正常情况下调用placement new 以在同一位置重新创建对象。
  • 您是否特别担心替换是在对象的方法内完成的,而且是虚拟方法,还是我们可以用更简单的方法替换您的示例代码,例如:@ 987654327@?

标签: c++ pointers destructor object-lifetime


【解决方案1】:

严格来说,这很好。但是,如果没有极度的照顾,它将成为一个可怕的UB。例如,任何调用此方法的派生类都不会重新构造正确的类型,或者如果Class() 抛出异常会发生什么情况。此外,这并没有真正完成任何事情。

这不是严格意义上的 UB,而是一大堆废话和失败,应该一见即焚。

【讨论】:

  • 您对潜在 UB 的评论很有价值,但您并没有真正提供任何论据来支持您“这很好”的说法。你能详细说明一下吗?
  • 有一个标准引用明确描述这种做法是合法的。具体来说,只要在正确的位置有正确类型的对象,指针就不是无效的——它是如何到达那里的,这不是它们的问题。
【解决方案2】:

指针只知道它的地址,一旦你确认新对象的地址就是指针指向的那个地址,答案是肯定的。

在某些情况下,人们会认为地址不会改变,但在某些情况下它确实会改变,例如使用 C 的 realloc() 时。但那是另一回事了。

【讨论】:

  • realloc 到底有什么关系?
  • 没什么,我只是想提醒一下,有时地址会发生变化。显然,这就是指针可能变得无效的唯一原因。在这种情况下,不是。
【解决方案3】:

object 指针在任何时候都不会失效(假设你的析构函数没有调用delete this)。您的对象从未被释放,它只调用了它的析构函数,即它已经清理了它的内部状态(关于实现,请注意标准严格定义对象在析构函数调用后被销毁)。由于您已使用placement new 在完全相同的地址实例化新对象,因此技术上没问题。

C++ 标准的第 3.8.7 节涵盖了这个确切的场景:

如果,在对象的生命周期结束之后并且在存储之前 被占用的对象被重用或释放,一个新的对象是 在原始对象占用的存储位置创建,a 指向原始对象的指针,引用的引用 到原始对象,或者原始对象的名称将 自动引用新对象,并且一旦生命周期 新对象已启动,可用于操作新对象[...]

也就是说,这只是作为学习代码有趣,作为生产代码,这太可怕了:)

【讨论】:

  • 严格来说,调用析构函数不会“清理内部状态”,它实际上会销毁对象(根据标准术语(§12.4))。但是,您关于仅在释放内存时才使指针失效的评论似乎是正确的(请参阅第 3.7.4.2 节)。即使指针指向的内存中不再有任何对象,内存仍然被分配,因此指针仍然有效(至少,这是我对标准的解释)。
  • 你是对的,但我主要是在描述真正发生在幕后的事情——我知道很多人认为解除分配在某种程度上与析构函数方法有关。我已经澄清了答案。
【解决方案4】:

在已销毁对象的位置创建新对象不会使任何指针再次有效。它们可能指向一个有效的新对象,但不是您最初引用的对象。

在销毁原始对象之前,您应该保证所有引用都已删除或以某种方式标记为无效。

这将是一个特别难以调试的情况。

【讨论】:

  • 同样不正确。一个指针只需要在解除引用时指向 一些 有效的对象(正确的类型)是有效的。在method 之前有效的指针和引用在method 之后有效。
  • @DeadMG:您的评论不正确。该对象可能不是正确的类型,这使得使用现有指针变得不可预测。指针可以被取消引用,但这不会使其有效。如果新对象的类型正确,它会使事情变得混乱并且程序行为不可靠。我的答案不需要“-1”。
  • 完全需要。如果他重构了正确的类型,则行为是明确定义的。难以调试的事实当然是正确的 - 但指向旧对象的指针无效是正确的。
  • 我的回答是正确的。 确实指向旧对象的指针无效因为没有旧对象!虽然指针当然可以被取消引用如果它指向一个正确类型的new对象,依赖于这个数据的操作的结果将是不正确的,因此,无效。您可能不喜欢我的回答,但请尊重您的反对意见。
【解决方案5】:

您可能需要重新考虑显式调用析构函数。如果您要执行的代码恰好位于析构函数中,则将该代码移至新方法并从析构函数调用该方法以保留当前功能。析构函数实际上是用于超出范围的对象。

【讨论】:

    【解决方案6】:

    这在 3.8:7 中得到明确批准:

    3.8 对象生命周期 [basic.life]

    7 - 如果在对象的生命周期结束后 [...],在原始对象占用的存储位置创建一个新对象,则指向原始对象的指针 [...] 可以用于操作新对象,如果:(在这种情况下满足各种要求)

    给出的例子是:

    struct C {
      int i;
      void f();
      const C& operator=( const C& );
    };
    const C& C::operator=( const C& other) {
      if ( this != &other ) {
        this->~C(); // lifetime of *this ends
        new (this) C(other); // new object of type C created
        f(); // well-defined
      }
      return *this;
    }
    

    【讨论】:

    • 各种要求中最繁重的可能是对象不能有任何const 或引用数据成员(它的成员也不能有,因为当你将这个技巧应用于一个对象时,你'重新将相同的技巧应用于其所有成员及其成员等)。这样的数据成员可能是导致某人认为“我不能改变对象,所以我需要销毁并重新创建它”的主要原因,但他们错了。
    • 很想对非异常安全的自赋值检查赋值运算符投反对票……但后来我读到它是标准中的示例。 -1 到标准。
    猜你喜欢
    • 2012-11-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-05-07
    • 1970-01-01
    • 1970-01-01
    • 2021-05-18
    • 1970-01-01
    相关资源
    最近更新 更多