29

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

{

    T x(31, Blue, false);

    x.~T();                        // enough with the old x

    ::new (&x) T(22, Brown, true); // in with the new!

    // ...
}

在作用域结束时,析构函数将再次运行,一切看起来都很好。(我们也可以说T有点特殊,不喜欢被分配,更不用说交换了。)但有件事告诉我,破坏一切并重试并非总是没有风险。这种方法有可能会遇到问题吗?

4

6 回答 6

28

我认为使它真正安全使用的唯一方法是要求被调用的构造函数是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。

于 2012-01-12T03:22:20.767 回答
17

如果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
}
于 2012-01-12T02:59:34.423 回答
9

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

于 2012-01-12T02:59:48.480 回答
7

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

@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... 
于 2012-01-12T03:43:40.500 回答
2

嗯。由于您正在做 C++ 不鼓励的所有事情,我认为每个人都忘记了goto

请注意,在显式X.~T()调用之后和重构之前1goto ,如果有人在变量 x 的声明/初始化之前(即使在内部范围块内)执行了 to ,仍然会有双重破坏。

由于您显然可以记录下来,因此我不会经历尝试“修复”此问题的麻烦。从概念上讲,您可以设计一个 RAII 类来就地管理对象重建,从而使这种操作在任何地方对 goto 都是安全的。请注意,您可以从 RAII 管理器对象的析构函数中完美地转发放置新的构造函数调用。生活很好。

当然,其他警告仍然适用(请参阅其他答案)


1我们可以假设此刻没有构造

于 2012-01-12T07:59:09.910 回答
0

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

于 2012-01-12T10:05:07.977 回答