57

In [except.ctor] the standard (N4140) guarantees that:

...destructors are invoked for all automatic objects constructed since the try block was entered...

However in the following example the empty output proves that the return value of function foo is not destructed, although it has been constructed. Compiled using g++ (5.2.1) and clang++ (3.6.2-1) and with options -O0 -fno-elide-constructors -std=c++14.

struct A { ~A() { cout << "~A\n"; } };

struct B { ~B() noexcept(false) { throw 0; } };

A foo() {
  B b;
  return {};
}

int main() {
  try { foo(); }
  catch (...) { }
}

Is this a bug both in g++ and clang++, or are function return values not considered automatic objects, or is it a loop hole in the C++ language?

In none of [stmt.return], [expr.call] or [dcl.fct] I have been able to find a clear statement whether a function return value is considered an automatic object. The closest hints I found are 6.3.3 p2:

...A return statement can involve the construction and copy or move of a temporary object...

and 5.2.2 p10:

A function call is an lvalue if the result type is an lvalue reference type or an rvalue reference to function type, an xvalue if the result type is an rvalue reference to object type, and a prvalue otherwise.

Community
  • 1
  • 1
Florian Kaufmann
  • 803
  • 6
  • 13

3 Answers3

45

Function return values are considered temporaries, and the construction of the return value is sequenced before the destruction of locals.

Unfortunately, this is underspecified in the standard. There is an open defect which describes this and offers some wording to fix the issue

[...] A return statement with an operand of type void shall be used only in a function whose return type is cv void. A return statement with any other operand shall be used only in a function whose return type is not cv void; the return statement initializes the object or reference to be returned by copy-initialization (8.5 [dcl.init]) from the operand. [...]

The copy-initialization of the returned entity is sequenced before the destruction of temporaries at the end of the full-expression established by the operand of the return statement, which, in turn, is sequenced before the destruction of local variables (6.6 [stmt.jump]) of the block enclosing the return statement.

Since function return values are temporaries, they aren't covered by the destructors are invoked for all automatic objects quote at the start of your post. However, [class.temporary]/3 says:

[...] Temporary objects are destroyed as the last step in evaluating the full-expression that (lexically) contains the point where they were created. This is true even if that evaluation ends in throwing an exception. [...]

So I think you could consider this a bug in GCC and Clang.

Don't throw from destructors ;)

Community
  • 1
  • 1
TartanLlama
  • 63,752
  • 13
  • 157
  • 193
  • 6
    I found that both gcc and clang already have this bug filed against them since years, so I don't expect that they will fix it soon: [gcc](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=33799), [clang](https://llvm.org/bugs/show_bug.cgi?id=12286). – Florian Kaufmann Jan 08 '16 at 19:06
  • Is compiler allowed to omit the very construction of object `A` in this case? I mean some rule similar to RVO. – Mikhail Jan 12 '16 at 13:55
  • @Mikhail I don't believe so. RVO can't eliminate construction completely, it can eliminate *intermediate* constructions. – TartanLlama Jan 12 '16 at 14:10
7

This is a bug, and for once, MSVC actually gets it right: it prints "~A".

Sebastian Redl
  • 69,373
  • 8
  • 123
  • 157
  • See some times clang and gcc rejects the code, but MSVC accepts the code with defined behavior. It happened to me once too, if asked I can provide example. – Angelus Mortis Jan 08 '16 at 09:45
  • 6
    Could yous please add a reference to the standard proving that a function return value is considered an automatic object, or which otherwise proves that it is indeed a bug in gcc and clang? – Florian Kaufmann Jan 08 '16 at 09:45
  • @FlorianKaufmann I think [this example](http://coliru.stacked-crooked.com/a/ee1a5d76230ec21c) shows that there's somewhere an `A` object for which the destructor is never called, even though it goes out of scope after having been created. – Antonio Jan 08 '16 at 09:48
  • @FlorianKaufmann TartanLlma's reference to a defect report below your post seems to exactly address the issue at hand (is the to-be-returned-temporary object of type A constructed before b, a local variable, is destructed?). The answer should be yes, but is underspecified. The answer is probably yes because of the obvious use case that locals are involved in putting the return value together. – Peter - Reinstate Monica Jan 08 '16 at 09:53
  • @FlorianKaufmann: It's essentially automatic by default - what else could it be? Not a global, not a member, not an exception object... – MSalters Jan 08 '16 at 15:04
  • 4
    @MSalters. 'What else could it be?' Right, I have the same feelings. However it is not a proof. I prefer to have confirmation and see the relevant paragraph in the standard. We know from experience that the C++ language has many surprises. – Florian Kaufmann Jan 08 '16 at 15:42
7

I modified your code and I think that now from the output we can see that A is not destructed.

#include<iostream>

using namespace std;

struct A {
    ~A() { cout << "~A\n"; }
    A() { cout << "A()"; }
};

struct B {
    ~B() noexcept( false ) { cout << "~B\n"; throw(0); }
    B() { cout << "B()"; }
};

A foo() {
    B b;
    return;
}

int main() {
    try { foo(); }
    catch (...) {}
}

And the output is:

B()A()~B

So yes it could be a bug.

p.i.g.
  • 2,815
  • 2
  • 24
  • 41
AC Voltage
  • 339
  • 3
  • 9