Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Destruction of return value on destructor exception

I have the following code:

#include <stdexcept>
#include <iostream>

struct ok {
    int _n;
    ok(int n) : _n(n) { std::cerr << "OK" << n << " born" << std::endl; }
    ~ok() {  std::cerr << "OK" << _n << " gone" << std::endl; }
};

struct problematic {
    ~problematic() noexcept(false) { throw std::logic_error("d-tor exception"); }
};

ok boo() {
    ok ok1{1};
    problematic p;
    ok ok2{2};
    return ok{3}; // Only constructor is called...
}

int main(int argc, char **argv) {
    try {boo();} catch(...) {}
}

I see that he destructor of ok{3} is not called, the output is:

 OK1 born
 OK2 born
 OK3 born
 OK2 gone
 OK1 gone

Is it the expected behavior for C++14?

EDITs:

Compiling with gcc 6.3

like image 666
Evgeny Avatar asked Nov 06 '18 07:11

Evgeny


People also ask

What happens when an exception is thrown in a destructor?

Throwing an exception out of a destructor is dangerous. If another exception is already propagating the application will terminate.

Can a destructor return a value?

Declaring destructorsDo not return a value (or void ). Cannot be declared as const , volatile , or static . However, they can be invoked for the destruction of objects declared as const , volatile , or static .

What will happen in case of destructor if the exception is raised inside the try block?

When an object is created inside a try block, destructor for the object is called before control is transferred to catch block. Question 9 Explanation: The destructors are called in reverse order of constructors. Also, after the try block, the destructors are called only for completely constructed objects.

Are destructors called on exception?

Yes, destructors are guaranteed to be called on stack unwinding, including unwinding due to exception being thrown. There are only few tricks with exceptions that you have to remember: Destructor of the class is not called if exception is thrown in its constructor.


Video Answer


1 Answers

As per the standard this behavior is wrong and this has already been mentioned in the comments section of the question. This is stated in the section on Exception handling.

As per the defect reports at open-std.org, they have been aware that implementations (GCC and Clang) were wrong about this as early as 2015-09-28. But the proposed resolution was only in February, 2016 and the compilers (GCC and Clang) have not yet included this fix.

Proposed resolution (February, 2016):

Change 18.2 [except.ctor] paragraph 2 as follows:
The destructor is invoked for each automatic object of class type constructed, but not yet destroyed, since the try block was entered. If an exception is thrown during the destruction of temporaries or local variables for a return statement (9.6.3 [stmt.return]), the destructor for the returned object (if any) is also invoked. The objects are destroyed in the reverse order of the completion of their construction. [Example:

  struct A { };

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

  A f() {
    try {
      A a;
      Y y;
      A b;
      return {};   // #1
    } catch (...) {
    }
    return {};     // #2
  }

At #1, the returned object of type A is constructed. Then, the local variable b is destroyed (9.6 [stmt.jump]). Next, the local variable y is destroyed, causing stack unwinding, resulting in the destruction of the returned object, followed by the destruction of the local variable a. Finally, the returned object is constructed again at #2. —end example]

There have been bugs filed against this issue both in GCC and Clang.

The comments on the GCC bug report indicate that it is clearly a bug.

Jonathan Wakely comments:

It's now 2013 so the sensible thing to do is not return by value if your destructor can throw.

And another user:

Yes, I noticed, and Clang has also had a bug filed against them which has languished for years. Nevertheless, the behavior is wrong.

like image 183
P.W Avatar answered Oct 18 '22 22:10

P.W