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

发布于 2024-12-26 11:37:40 字数 351 浏览 0 评论 0原文

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

{

    T x(31, Blue, false);

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

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

    // ...
}

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

Sometimes it's nice to start over. In C++ I can employ this following simple manoeuvre:

{

    T x(31, Blue, false);

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

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

    // ...
}

At the end of the scope, the destructor will run once again and all seems well. (Let's also say T is a bit special and doesn't like being assigned, let alone swapped.) But something tells me that it's not always without risk to destroy everything and try again. Is there a possible catch with this approach?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

雨落□心尘 2025-01-02 11:37:40

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

I think the only way to make this really safe to use is to require the called constructor to be noexcept, for example by adding a 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);

Of course this will only work for C++11.

一个人的旅程 2025-01-02 11:37:40

如果 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
}

If T's constructor throws on the second construction, you got a problem. If you like brute-force approaches, check this:

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!
  }
}

It even provides the strong exception guarantee!


On a more serious note, it's like stepping on a landmine, knowing it will go off if you move your foot...

And there actually is a way around the undefined behaviour of the double destruction here:

#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
}
携君以终年 2025-01-02 11:37:40

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

If T's construction expression throws, you will double destruct the object, which is UB. Of course, even the desire to do this is indicative of a design failure.

巴黎盛开的樱花 2025-01-02 11:37:40

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

@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... 

I tried to compile it, but I only dared to run it under debugger. So I took a look at disassembly my old compiler generated (comments are compiler's too):

@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... 
猫性小仙女 2025-01-02 11:37:40

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

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

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

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


1我们现在可以假设没有抛出构造

Mmm. Since you're doing everything that C++ discourages, I think everyone is forgetting about goto.

Note that after the explicit X.~T() call, and before it is reconstructed1, there would still be double destruction if someone did a goto to before the declaration/initialization of the variable x (even within the inner scope block).

Since you could obviously just document that, I won't go through the hassle of trying to 'fix' this. You could, conceptually, design a RAII class to manages object re-construction in-place, making this manoeuvre safe for goto's in any place. Note that you could have the placement-new constructor call get perfectly forwarded from the RAII manager object's destructor. Life is good.

The other caveats still apply, of course (see other answers)


1 we can assume nothrow constuction for this moment

夜访吸血鬼 2025-01-02 11:37:40

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

There is nothing to stop you doing this, it will work in most cases. But as is the case in a lot of C++ knowing the specifics of your your cases will be the difference between it working as you want and a core dump. There are very few example of reasons I could see why you would want to do this in a real program, the only one that make sense is a memory mapped file.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文