2

Normally you are responsible for lifetime of your unrestricted union members -- and typically you do it via in-place ctor/dtor calls. But, apparently, there is at least one case when compiler helps you -- in the code below, if object construction fails it's (previously constructed) union member gets automatically destroyed (at least in MSVC 2015), i.e. we never leak.

#include <string>

struct CanThrow
{
    CanThrow() {  throw 0;  }
};

struct A
{
    A() : str{} {}    // note that we don't explicitly call str dtor here
    ~A() { str.~basic_string(); }

    union { std::string str; };
    CanThrow ct;
};

int main() { try{ A a; } catch(...) {} }

Disclaimer: this code compiles on my MSVC 2015

Question -- is this guaranteed by standard and where it stipulates that?

C.M.
  • 3,071
  • 1
  • 14
  • 33

1 Answers1

1

Quite the contrary: it's not supposed to happen!

[C++11: 9.5/8]: A union-like class is a union or a class that has an anonymous union as a direct member. A union-like class X has a set of variant members. If X is a union its variant members are the non-static data members; otherwise, its variant members are the non-static data members of all anonymous unions that are members of X.

[C++11: 15.2/2]: An object of any storage duration whose initialization or destruction is terminated by an exception will have destructors executed for all of its fully constructed subobjects (excluding the variant members of a union-like class), that is, for subobjects for which the principal constructor (12.6.2) has completed execution and the destructor has not yet begun execution. Similarly, if the non-delegating constructor for an object has completed execution and a delegating constructor for that object exits with an exception, the object’s destructor will be invoked. If the object was allocated in a new-expression, the matching deallocation function (3.7.4.2, 5.3.4, 12.5), if any, is called to free the storage occupied by the object.

If Visual Studio is doing so, it's non-compliant; FWIW, GCC 6.3 seems to be also.

Note that the (current) C++17 wording is different and does permit what we're observing; this change appears to have been introduced by CWG issue #1866 in 2014, so it's likely that this is one of those times that the main compilers were "ahead of the game" and don't quite abide to the standard (despite -std flags).

So the answer is, no, the standard certainly doesn't guarantee it in MSVS 2015's case, though it will in future, C++17 versions of the software.

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
  • it definitely happens in MSVC2015. Checked in debugger using breakpoint in ~basic_string() – C.M. Apr 22 '17 at 23:08
  • GCC is broken then too -- add try/catch around construction in main(). your example fails because escaped exception doesn't unwind stack. Sorry about that -- I should'be added it. – C.M. Apr 22 '17 at 23:11
  • @C.M.: Er good point lol then yes both would appear to be broken unless I'm misreading something. – Lightness Races in Orbit Apr 22 '17 at 23:12
  • Clang too. Getting suspicious now. – Lightness Races in Orbit Apr 22 '17 at 23:13
  • don't take me wrong -- I like this behavior, tbh. I just want to make sure I can rely on it :) – C.M. Apr 22 '17 at 23:15
  • I'm investigating further. Kerrek's answer may suggest that the wording was relaxed in future standard versions/drafts, possibly to match what the major implementations _actually_ were doing. However, [a proposal to do this was previously rejected](https://wg21.cmeerw.net/cwg/issue1624). In the meantime I'm sticking with my answer that, surprisingly, all three appear non-compliant in this regard. – Lightness Races in Orbit Apr 22 '17 at 23:19
  • @C.M.: Final answer in place now; take it or leave it ;) – Lightness Races in Orbit Apr 22 '17 at 23:24
  • 1
    it looks like this whole feature is completely [broken](http://coliru.stacked-crooked.com/a/c495397779784e3e)... Why I keep running into issues like that in the most basic building blocks of the language? – C.M. Apr 23 '17 at 00:26
  • @C.M.: Just lucky I guess! And yes that's rather spectacular, lol – Lightness Races in Orbit Apr 23 '17 at 00:29
  • 1
    btw, compilers are trying to do the right thing -- not calling dtor in this situation is a language defect... If I have three such unions in my class and try to construct all of them in ctor initializer list -- there is no way for me to tell which one needs to be destroyed in case of exception. – C.M. Apr 23 '17 at 00:30
  • @C.M.: I just avoid unions. `boost::variant` seems to work very well and other than that I avoid the beggars. – Lightness Races in Orbit Apr 23 '17 at 00:30
  • Well, last time I ran into smth like this it was a function call -- hard to avoid these :). [Here](http://coliru.stacked-crooked.com/a/bd6f7022343a70fc) is what I was trying to do with unions (simplified, of course). – C.M. Apr 23 '17 at 00:53
  • @C.M.: No problem and good luck. FWIW I can't really see the value of a union in your example unless you're on an exceptionally constrained system – Lightness Races in Orbit Apr 23 '17 at 01:28
  • i could get away with another allocation and pointers, but union is a perfect fit for a problem. I like code where everything fits just right. Thanks for help. – C.M. Apr 23 '17 at 02:43
  • Doesn't look like it's fitting very well :P – Lightness Races in Orbit Apr 23 '17 at 13:30
  • Idea fits just fine. It's implementation (by the language as of now) -- no so much. But I am glad compilers made the effort -- I just need to keep in mind the "broken case". I wonder what C++17 has to say about destroying union member in main body of parent object ctor. :) – C.M. Apr 23 '17 at 18:34