3
struct Base {
    Base() {
        std::cout << "Inside:  " << __PRETTY_FUNCTION__ << std::endl;
    }
    ~Base() {
        std::cout << "Inside:  " << __PRETTY_FUNCTION__ << std::endl;
    }
};

struct BaseWrapper {
    const Base &b;
};

int main()
{
    {
        auto *w = new BaseWrapper{{}};
        std::cout << "Inside:  " << __PRETTY_FUNCTION__ << std::endl;
        delete w;
    }
    return 0;
}

The above code works as I expected when I compile it with C++11 or C++14, but when I compile it with C++17, it gives me something like this:

Inside:  Base::Base()                                                                                                                                                              
Inside:  int main()

As you can see, Base::~Base() was never called. Which doesn't make much sense to me. I have tested this code with GCC 7.3.0 on Ubuntu 18.04.1 LTS, and also with OnlineGDB. They all give the same result.

I'm just wondering if this is a new feature in C++17 or it is a bug?

Update: I'm well aware that w->b is a dangling reference. Actually, I deliberately wrote this piece of code just to show that. Then, I found this issue while testing it.

What I really want to know is how severe this issue is, in case I have to stick with GCC 7.3? or if there is any other ways to reproduce the same issue? or the defect report?

Yang Yang
  • 411
  • 3
  • 11
  • Try gcc-8, which does have the destructor. – Marc Glisse Mar 06 '19 at 01:47
  • 1
    Given that `b` is a reference (and not inside a function), I expect B not to be destroyed in that case. – Phil1970 Mar 06 '19 at 01:48
  • When I try this on [TIO](https://tio.run/##tVBLa8JAEL7nVwwWxAeCngpJ9NBSwYtKSSk9LevsKAtxE/ZBDiH@dLfZNND8AP0O82Lmm28Gy3JxQfQvUmHuBEEqC2M18evGt96hhTduCOoIWoRwMu2TAGNFHGPhLKQpjHbKSEExwCikjB0/P7Lsh22/9u/Z7rBnLJS7EVIiTzqWprO3pzA3SRQNjvjWvCxJ90uwUKY/bnxKul6pLFy5VJNp9NfzL4c7W8CsgjUoqoZsdd00yQNVBwjKyRJUww9psk4rWLZKvb/jOecX4xft8Brn89XrLw) it invokes the destructor (both for gcc linked, and clang, with `-std=c++17`). `clang` warns "warning: temporary bound to reference member of allocated object will be destroyed at the end of the full-expression [-Wdangling-field]". Might be cause of problem. – ShadowRanger Mar 06 '19 at 01:51
  • 1
    [Cannot reproduce](https://wandbox.org/permlink/EOM0zd66LzEsHTjm) – Kerrek SB Mar 06 '19 at 01:51
  • 1
    @KerrekSB Easy to reproduce from there: Just change wandbox's C++ version drop-down to "gcc 7.3.0" as the OP specified. The destructor output disappears. – aschepler Mar 06 '19 at 01:58
  • Thanks all for the comments. Looks like it is a bug that has already been fixed. If anyone know if there is a defect report for this. I'd be glad to accept that as an answer. – Yang Yang Mar 06 '19 at 02:06
  • 2
    @EvenYoung: This is a related bug: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=82560 and so is this: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=83802 – P.W Mar 06 '19 at 05:56
  • @P.W: That is really good information to know. Unfortunately, they are not the same issue as mine. – Yang Yang Mar 06 '19 at 06:46

2 Answers2

2

Clearly, a temporary that is created but not destroyed is a compiler bug - unless the standard says the behaviour is undefined. The example is well defined however. The relevant rules from the standard [class.temporary]:

When an implementation introduces a temporary object of a class that has a non-trivial constructor ([class.default.ctor], [class.copy.ctor]), it shall ensure that a constructor is called for the temporary object. Similarly, the destructor shall be called for a temporary with a non-trivial destructor ([class.dtor]). Temporary objects are destroyed as the last step in evaluating the full-expression ([intro.execution]) that (lexically) contains the point where they were created. ...

There are three contexts in which temporaries are destroyed at a different point than the end of the full-expression. ...

The third context is when a reference is bound to a temporary object ...

The exceptions to this lifetime rule are:

  • ...

  • A temporary bound to a reference in a new-initializer ([expr.new]) persists until the completion of the full-expression containing the new-initializer.

Community
  • 1
  • 1
eerorika
  • 232,697
  • 12
  • 197
  • 326
0

Looks like a bug, maybe with reference lifetime extension and heap allocated objects.

There are strange lifetime extension rules with aggregate initialization of reference members.

You can upgrade your compiler or simply add a BaseWrapper(Base const& bin):b(bin){} ctor and watch the bug evaporate.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • 1
    Even `auto *w = new BaseWrapper{Base{}};` will solve this issue. – aep Mar 06 '19 at 03:45
  • @aep sure, but that requires audoting all ise of base wrapper. I would rather want basewrapper to be safe to use and not insane. Then again, dangling references are insane – Yakk - Adam Nevraumont Mar 06 '19 at 09:39