Home » C++ » Are function return values automatic objects and thus guaranteed to be destructed?

Are function return values automatic objects and thus guaranteed to be destructed?

Posted by: admin November 30, 2017 Leave a comment

Questions:

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.

Answers:

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 😉

Questions:
Answers:

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

Questions:
Answers:

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.