销毁,然后使用相同的变量构造新对象

Destroy and then construct new object using the same variable

本文关键字:新对象 对象 变量 然后 销毁      更新时间:2023-10-16

有时重新开始很好。在C++中,我可以使用以下简单的操作:

{
    T x(31, Blue, false);
    x.~T();                        // enough with the old x
    ::new (&x) T(22, Brown, true); // in with the new!
    // ...
}

在作用域结束时,析构函数将再次运行,一切似乎都很好。(也可以说T有点特别,不喜欢被分配,更不用说交换了。)但有一件事告诉我,摧毁一切并再试一次并非总是没有风险的。这种方法可能有陷阱吗?

我认为让它真正安全使用的唯一方法是要求被调用的构造函数为noexcept,例如添加static_assert:

static_assert(noexcept(T(22, Brown, true)), "The constructor must be noexcept for inplace reconstruction");
T x(31, Blue, false);
x.~T();
::new (&x) T(22, Brown, true);

当然,这只适用于C++11。

如果T的构造函数抛出第二个构造,则会出现问题。如果你喜欢暴力方法,请查看以下内容:

T x(31, Blue, false);
x.~T();
const volatile bool _ = true;
for(;_;){
  try{
    ::new (&x) T(22, Brown, true);
    break; // finally!
  }catch(...){
    continue; // until it works, dammit!
  }
}

它甚至提供了强大的异常保证!


更严重的是,这就像踩在地雷上,知道如果你移动你的脚,地雷就会爆炸。。。

实际上,有一种方法可以绕过双重破坏的未定义行为:

#include <cstdlib>
T x(31, Blue, false);
x.~T();
try{
  ::new (&x) T(22, Brown, true);
}catch(...){
  std::exit(1); // doesn't call destructors of automatic objects
}

如果T的构造表达式抛出,则将对对象进行双重析构,即UB。当然,即使是这样做的愿望也表明了设计的失败。

我试图编译它,但我只敢在调试器下运行它。所以我看了一下我的旧编译器生成的反汇编(注释也是编译器的):

@1 sub nerve.cells, fa0h
@2 xor x, x     // bitch.
@3 mov out, x
@4 test out, out
@5 jne @1
@6 xor x, x     // just in case.
@7 sub money, 2BC   // dammit.
@8 mov %x, new.one
@8 cmp new.one, %x 
@9 jne @7   
...
@25 jmp @1      // sigh... 

Mmm。由于您正在做C++不鼓励的事情,我想每个人都忘记了转到

请注意,在显式X.~T()调用之后,在重构1之前,如果有人在变量x的声明/初始化之前执行goto甚至在内部作用域块内),则仍将存在双重破坏。

既然你显然可以把它记录下来,我就不会再麻烦地试图"解决"这个问题了。从概念上讲,您可以设计一个RAII类来管理对象的重新构建,使这种操作在任何地方都对后藤来说都是安全的。请注意,您可以让placement新构造函数调用从RAII管理器对象的析构函数得到完美的转发。生活是美好的。

当然,其他注意事项仍然适用(见其他答案)


1我们可以假设此时的另一个行构造

没有什么可以阻止你这样做,它在大多数情况下都会起作用。但是,正如许多C++的情况一样,知道你的案例的细节将是它按你想要的方式工作和核心转储之间的区别。我能理解为什么你想在一个真实的程序中这样做的原因的例子很少,唯一有意义的是内存映射文件。