29

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?

jww
  • 97,681
  • 90
  • 411
  • 885
Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
  • 7
    Why the heck would you want to do this? – R. Martinho Fernandes Jan 12 '12 at 02:58
  • This is a whimsical approach to pedagogy. – Crashworks Jan 12 '12 at 02:58
  • 8
    What's wrong with just `x = T(22, Brown, true);`? – Karl Knechtel Jan 12 '12 at 02:59
  • 29
    It's well known idiom. `31` and `22` are ages, `Blue` and `Brown` - color of the eyes, `false` and `true` - either `love`, but most likely breasts. Bottom line, somewhere along the lines (of your code) your new girlfriend will become `x` too. – lapk Jan 12 '12 at 03:01
  • 4
    @AzzA: I was literally just picking standard type and variable names and some random integers from the lazy end of my keyboard and random bools.. does this say more about my subconsciousness or about you now? :-S – Kerrek SB Jan 12 '12 at 03:04
  • 3
    @AzzA: And what if `31` and `22` refer to the number of tentacles, `Blue` and `Brown` to the skin color, and `false` and `true` are about having the idea of conquering human-kind? – Xeo Jan 12 '12 at 03:04
  • 1
    @KarlKnechtel: Oh, `T` doesn't have an ass. operator. Let me edit. – Kerrek SB Jan 12 '12 at 03:07
  • @KerrekSB Well, I guess we are both in trouble then. But I cannot see how it can be read differently... or typed by an accident ;) – lapk Jan 12 '12 at 03:07
  • Of course `T` doesn't like being assigned.. It doesn't want to become another `T`, it just wants a fresh start! – Xeo Jan 12 '12 at 03:09
  • 11
    @Xeo, You obviously didn't see author's comments next to code. Not to mention his choice of local variable name and the fact that he subconsciously ALREADY prepared to fail - he is creating new girlfriend at the old one's address... How silly is that? – lapk Jan 12 '12 at 03:12
  • 2
    Why not `T x(31, Blue, false); T y(22, Brown, true); std::swap(x, y);` ? – johnsyweb Jan 12 '12 at 03:26
  • 18
    @AzzA: You're right, maybe I *am* subconsciously treating objects like women... – Kerrek SB Jan 12 '12 at 03:28
  • 7
    @Kerrek: hmmm... better than treating women like objects. But, I can see why you have to be worried about them throwing things. – Tony Delroy Jan 12 '12 at 03:33
  • 1
    @Johnsyweb: if `T` is not assignable `std::swap` will likely fail (we can't assume that it has a custom swap). Besides for a generic class don't you mean `using std::swap; swap(x, y)`. – Grizzly Jan 12 '12 at 03:41
  • @Grizzly: Keep in mind in C++11, `std::swap(x, y)` is required to have the same behavior as `using std::swap; swap(x, y);`, so we can stop worrying. :) – GManNickG Feb 06 '12 at 07:41
  • @GMan: It is? I didn't know that. Can you tell me where in the standard that behaviour is mandated? Not that I don't trust you, but I would like to verify for myself. – Grizzly Feb 06 '12 at 14:08
  • @Grizzly: §17.6.3.2/3. In the meantime, `boost::swap` does the same. – GManNickG Feb 06 '12 at 16:50
  • @GMan: As far as I can tell that section says nothing of that sort. As I understand it, it says that `a` and `b` are considered swappable if `swap(a, b)` is valid in a context where `std::swap` can be called unqualified. Considering that the examples in the section show `using std::swap; swap(t1, t2);` for how to ensure the proper evaluation context and that `§20.2.2` doesn't mention any such behavior your statement doesn't seem to be correct (I might be wrong of course). `boost::swap` behaves that way, though I wouldn't include boost just for that (unless I already use it in the code). – Grizzly Feb 06 '12 at 22:28
  • @Grizzly: Just read the first sentence of paragraph three: "The context in which `swap(t, u)` and `swap(u, t)` are evaluated shall ensure that a binary non-member function named “`swap`” is selected via overload resolution". This is a requirement of the standard library. The standard is saying, "Your implementation of the standard library better find a binary non-member `swap` if it exists when you call `swap(t, u)`. (And paragraph two says that swappable types are to be called with unqualified `swap`, not `std::swap`.) – GManNickG Feb 06 '12 at 23:56
  • @Grizzly: And just because I'm lame, [here's an argument from authority](http://stackoverflow.com/a/2684544/87234) (in the comments). It's been the intention for a long time that `std::swap` internally searches for `swap(t, u)` with ADL. – GManNickG Feb 07 '12 at 00:07
  • @GMan:The paragraph basically states that when the standard library uses `swap` it shall search for one using ADL (meaning it has to call it as `swap(t, u)` instead of `::std::swap(t, u)` or some `__Swap(t, u)`). That doesn't mean that `std::swap` will internally use ADL to find an existing `swap`. From the text and particulary the examples in the standard I'm pretty sure it doesn't. The provided link doesn't say anything about such a behaviour of `std::swap` either, only about using `swap` in the library. Changing that behaviour would be like a pretty sever break in backward compatibility. – Grizzly Feb 07 '12 at 01:28
  • @Grizzly: I don't think that's right. Perhaps I'll ask a question. – GManNickG Feb 07 '12 at 01:40
  • @Grizzly: [I asked](http://stackoverflow.com/questions/9170247/does-c11-change-the-behavior-of-explicitly-calling-stdswap-to-ensure-adl-loc). You do seem to be correct on further review, which makes me sad two-fold. – GManNickG Feb 07 '12 at 02:35
  • Does this answer your question? [Call destructor and then constructor (resetting an object)](https://stackoverflow.com/questions/1124634/call-destructor-and-then-constructor-resetting-an-object) – anton_rh May 15 '22 at 17:41

6 Answers6

28

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.

Grizzly
  • 19,595
  • 4
  • 60
  • 78
  • 7
    Very nice use of `noexcept` - I never realized it could be used interrogatively! – Kerrek SB Jan 12 '12 at 03:23
  • 1
    You could package the `static_assert`, pseudo-destructor call, and placement new all into one `reconstruct` function, using perfect forwarding for the last step. But I still think it's awful form. – Potatoswatter Feb 06 '12 at 05:15
  • Unfortunately this doesn't work for classes that have const or reference members: [link](https://stackoverflow.com/a/50221156/5447906). At least until C++20: [link](https://stackoverflow.com/a/72250913/5447906). – anton_rh May 16 '22 at 13:25
17

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
}
Xeo
  • 129,499
  • 52
  • 291
  • 397
  • 1
    Hmm... and the landmine would be a class which throws on every but its first constructor call! – Kerrek SB Jan 12 '12 at 03:01
  • Of course depending on your type T this might loop on for all eternity (if the constructor always throws for those arguments), so it's "safe for certain definitions of safe" (strong guarantee or not I'm not convinced I'd like that in my code) – Grizzly Jan 12 '12 at 03:01
  • My usual workaround was to make an aligned buffer and placement new to start. Then you avoid the automatic destruction if the re-construction fails, avoiding the pitfall. – Mooing Duck Jan 12 '12 at 03:19
  • 2
    Why use `_` at all? `for(;;)` is defined to be an infinite loop. – GManNickG Jan 12 '12 at 03:22
  • 8
    @GMan: That part was meant as more of a humorous answer, that's why the `(;_;)` emoticon. :) Also, a `_` macro would be reserved for the implementation. :( – Xeo Jan 12 '12 at 03:23
  • 2
    @Xeo: I see, carry on. :) ☆☆☆ – GManNickG Jan 12 '12 at 03:27
  • 1
    @Xeo: I don't think `_` is reserved for the implementation; it neither matches `_[A-Z]` nor `__`. Cf. the `_1` placeholders from Boost and C++11 – MSalters Jan 12 '12 at 09:25
  • @MSalters: Identifiers starting with an underscore are reserved in the global namespace / scope (aka macros). – Xeo Jan 12 '12 at 14:54
  • @Xeo: Macros have their own separate namespace (§16.3/7); this is different from the global namespace accessed by `::` (§3.3.6/3). It's not so clear to me whether these macro names are reserved, or just likely to break an implementation's extensions to the Standard. (The Standard does not define any such names, and if it did, they would be protected by §17.6.4.3.1/1.) – Potatoswatter Feb 06 '12 at 05:30
9

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.

Puppy
  • 144,682
  • 38
  • 256
  • 465
  • Design failure, or moral failure? – Crashworks Jan 12 '12 at 03:05
  • @Crashworks: Rather, self-esteem failure. You know, starting over and such... ;) – Xeo Jan 12 '12 at 03:05
  • A question: It seems like a call to ~T() is special in that it will (obviously) call any code you've explicitly put in your destructor, and will also do a lot of automatic destruction of stuff? I had previously thought that the automatic stuff just happened around `delete` and and the end of the scope? Something new learned. – Aaron McDaid Jan 12 '12 at 03:47
  • @Aaron `delete` invokes the destructor and then releases allocated memory. Calling `~T()` explicitly only invokes the destructor (there is no allocated memory to release in the example). Doing this sidesteps normal automatic storage semantics, so the destructor will still be called anyway when control leaves the scope, even though you already called it manually. – R. Martinho Fernandes Jan 12 '12 at 03:54
  • I understand that. By 'automatic destruction of stuff' I meant calling the destructors of data members. A call to `delete` does three things, (1) execute the code you have written inside `~T()`, (2) calls destructors on data members, and (3) frees the memory. I was surprised that, if you explicitly call `~T()`, that you get (1) *and* (2). I had thought you would just get (1). – Aaron McDaid Jan 12 '12 at 04:05
  • @Aaron: definitely 1 and 2. Of course 2 also covers base class destruction if necessary (and associated implementation steps like VDT-pointer restoration to pre-derived-class-construction states as it works its way up the hierarchy towards the base classes). – Tony Delroy Jan 12 '12 at 05:16
  • I had thought that the compiler might sneak in another special function (call it `~~T`, for arguments sake) into the vtables and so on. I thought that this function would walk up the chain, do the automatic destruction of members, calling `~T` on the way. And that `~~T` would be called by `delete` and also when things go out of scope. .... OK, I'll admit, I didn't really think about it that much prior to seeing this question :-) . – Aaron McDaid Jan 12 '12 at 05:29
7

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... 
lapk
  • 3,838
  • 1
  • 23
  • 28
2

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

sehe
  • 374,641
  • 47
  • 450
  • 633
0

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.

David Allan Finch
  • 1,414
  • 8
  • 20