销毁并重新创建一个对象会使指向该对象的所有指针无效吗?

9
这是对这个问题的跟进。假设我有以下代码:
class Class {
    public virtual method()
    {
        this->~Class();
        new( this ) Class();
    }
};

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

这是该回答的简化版本。

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

这会使得调用对象的指针再次有效吗?


使指针无效不会完全破坏放置new的目的,对吗? - Luc Touraille
2
为什么这个函数是虚函数?这有点可怕。 - Kerrek SB
1
另外,如果“Class”的构造函数抛出异常,你就会陷入道德困境。(而且请不要将你的类命名为“Class”...) - Kerrek SB
@Luc Touraille:嗯,我不知道有哪些理智的情况下会调用放置 new 来在相同位置重新创建对象。 - sharptooth
@Luc Touraille:主要问题是该指针与对象之间没有“向后”的连接,因此它应该是一个成员函数、自由函数或另一个存储相同地址的指针变量。 - sharptooth
显示剩余2条评论
6个回答

8

这在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;
}

2
可能最繁琐的要求之一是对象不得具有任何const或引用数据成员(也不能其成员具有,因为当你将此技巧应用于一个对象时,你正在将相同的技巧应用于其所有成员及其成员等等)。这些数据成员可能是导致某人认为“我无法改变对象,所以需要销毁和重新创建它”的主要原因,但他们是错误的。 - Steve Jessop
2
太诱人了,想给那个非异常安全的自我赋值检查赋值运算符投反对票...但是后来我发现这是标准中的例子。对标准打-1分。 - Puppy

4

严格来说,这是可以的。但是,如果没有极度小心,它将变成一个可怕的UB(未定义行为)。例如,任何调用此方法的派生类都无法重新构造正确的类型-或者当Class()抛出异常时会发生什么。此外,这实际上并没有实现任何东西。

虽然不是严格的UB,但它是一堆垃圾和失败,应该立即焚毁。


你对潜在UB的评论很有价值,但你并没有提供任何支持你声称“这是好的”的论据。你能详细说明一下吗? - Luc Touraille
有一个标准引用明确描述了这种做法是合法的。具体来说,只要在正确的位置有正确类型的对象,指针就不会无效-它们如何到达那里并不是他们的问题。 - Puppy

4
object指针在任何时候都不会失效(假设您的析构函数没有调用delete this)。您的对象从未被释放,它只是调用了它的析构函数,也就是说,它已经清理了它的内部状态(关于实现,请注意标准严格定义了对象在析构函数调用后被销毁)。由于您使用了placement new在完全相同的地址实例化新对象,因此技术上是可以的。
C++标准的第3.8.7节涵盖了这种情况:

如果在对象的生命周期结束之后,在重新使用或释放原始对象占用的存储器之前,在原始对象所占用的存储位置上创建一个新对象,则指向原始对象的指针、引用原始对象的引用或原始对象的名称将自动引用新对象,并且一旦新对象的生命周期开始,就可以用来操作新对象[...]

话虽如此,这仅仅是学习代码的有趣案例,作为生产代码,这是可怕的 :)

严格来说,调用析构函数并不是“清理内部状态”,它实际上是销毁对象(根据标准术语(§12.4))。然而,你关于指针仅在内存被释放时才失效的评论似乎是正确的(参见§3.7.4.2)。即使指针所指向的内存中已经没有任何对象,但该内存仍然被分配,因此指针仍然有效(至少这是我对标准的解释)。 - Luc Touraille
你说得没错,但我主要是在描述底层实际发生的情况——我知道有很多人认为解除分配与析构函数方法有所关联。我已经澄清了答案。 - Zdeslav Vojkovic

0

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

有些情况下,人们会认为地址不会改变,但在某些情况下确实会改变,例如使用C语言的realloc()函数。但这是另外一个故事了。


realloc 究竟与任何事情有什么关系? - Puppy
没什么,我只是想提醒有时候地址会变化。这也是指针失效的唯一原因,很明显,在这种情况下并不是。 - ypnos

0
你可能需要重新考虑显式地调用析构函数。如果有一些代码需要在析构函数中执行,那么将该代码移动到一个新的方法,并从析构函数中调用该方法以保留当前功能。析构函数真正设计的目的是用于对象离开作用域时使用的。

-2
创建一个新对象并不会让所有指针重新变得有效,它们可能指向一个有效的新对象,但不是你最初引用的那个对象。
在销毁原始对象之前,您应该保证所有引用都已被移除或以某种无效的方式标记。
这将是一个特别困难的调试情况。

3
“也是错误的。指针只需要在解引用时指向某个有效对象(类型正确)即可有效。在‘method’之前有效的指针和引用,在‘method’之后仍然有效。” - Puppy
@DeadMG:您的评论是不正确的。对象可能不是正确的类型,这使得使用现有指针变得不可预测。指针可以被取消引用,但这并不意味着它是有效的。如果新对象是正确的类型,那么会让事情变得混乱,程序行为也会变得不可靠。对我的回答打“-1”是不应该的。 - Suncat2000
这是完全必要的。如果他重构了正确的类型,那么行为就是明确定义的。虽然调试很困难,但指向旧对象的指针并不无效。 - Puppy
我的答案是正确的。指向旧对象的指针无效,因为根本没有旧对象!尽管如果指针指向正确类型的新对象,则可以对其进行解引用,但依赖于此数据的操作的结果将不正确,因此是无效的。您可能不喜欢我的答案,但请恭敬地移除您的负评。 - Suncat2000

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接