Constructor as a Function Try Block - Exception Aborts Program

Constructor as a function try block - Exception aborts program

There's a relevant gotw

http://gotw.ca/gotw/066.htm

Basically even if you don't throw in your catch block, the exception will automatically be rethrown

If the handler body contained the statement "throw;" then the catch
block would obviously rethrow whatever exception A::A() or B::B() had
emitted. What's less obvious, but clearly stated in the standard, is
that if the catch block does not throw (either rethrow the original
exception, or throw something new), and control reaches the end of the
catch block of a constructor or destructor, then the original
exception is automatically rethrown.

When is a function try block useful?

You use it in constructors to catch errors from initializers. Usually, you don't catch those errors, so this is a quite exceptional use.

Otherwise, it is useless: unless I'm proven wrong,

void f() try { ... } catch (...) { ... }

is strictly equivalent to

void f() { try { ... } catch (...) { ... } }

An exception gets thrown twice from a constructor with a function-try-block

It seems logical. Consider two following scenarios.

i. Try block is inside constructor's body:

  A() : i(0) {
try
{
throw E("Exception thrown in A()");
}
catch (E& e) {
cout << e.error << endl;
}
// If code reaches here,
// it means the construction finished well
}

ii. Try block is in initializer ctor:

  A() try : i(0) {
throw E("Exception thrown in A()");
}
catch (E& e) {
cout << e.error << endl;

// OK, you handled the exception,
// but wait you didn't construct the object!
}

In the first case, after an exception, you will handle it inside the constructor and then you will construct the object properly.

In the second case, after an exception you will handle it there. BUT you didn't construct the object yet and you have no object in the caller's side. The caller should handle an un-constructed object situation.

Why does an exception thrown in a constructor fully enclosed in try-catch seem to be rethrown?

cppreference has this to say about a function-try-block (which is what we have here):

Every catch-clause in the function-try-block for a constructor must terminate by throwing an exception. If the control reaches the end of such handler, the current exception is automatically rethrown as if by throw.

So there we have it. Your exception is automatically rethrown when the catch on the constructor's member initialization list exits. I guess the logic is that your constructor is deemed to have failed so (after the exception handler in the constructor performs any cleanup, perhaps) the exception is automatically propagated to the caller.

Catch Exception from constructor without hiding the object in the try block

A solution that doesn't require changing A is to use nested try/catch blocks:

try {
A a1;
try {
A a2;
do_something(a1, a2);
}
catch {
// a2 (or do_something) threw
}
} catch {
// a1 threw
}

Probably better to avoid doing this if possible though.

Should function-try-block of d'tor allow handling of throwing member-variable d'tor?

~NonThrowingDtor() noexcept try {
x.willThrow = false;
} catch (...) {
// Ignore because we know it will never happen.
}

is "wrong" as equivalent to

~NonThrowingDtor() noexcept try {
x.willThrow = false;
} catch (...) {
throw;
}

so simply

~NonThrowingDtor() noexcept
{
x.willThrow = false;
}

To not propagate exception, you have to use explicitly return:

~NonThrowingDtor() noexcept try {
x.willThrow = false;
} catch (...) {
return; // Required to not propagate exception.
}

Unfortunately, msvc still warns with this form which doesn't throw.

(On the other side, clang/gcc don't warn for implicit (but do for explicit) throw in that case).

Return statement in a handler of function-try-block of a constructor

A function-try-block is a try block that wraps outside the entire body of a function or constructor, rather than just a section of code inside of the body.

In the case of a constructor, it allows you to catch an exception that is thrown while initializing base classes and data members within the initialization list. If such an exception is thrown, you can catch it and process the error as needed, but you cannot return from the catch block. When execution reaches the end of the catch block, the current exception is automatically rethrown. Before the catch is entered, any base class and data member that was successfully constructed before the initial exception was thrown has already been destructed to prevent leaks.

As the standard says:

If a return statement appears in a handler of the function-try-block of a constructor, the program is ill-formed.

In this case, the handler is referring to a catch block of the try block.

For example:

int checkValue(int value)
{
if (value == 1)
throw std::runtime_error("illegal value");
return value;
}

struct A
{
std::string str;
int value;

A(int i) try : str("hello"), value(checkValue(i))
{
// constructor body here ...
// 'return' is OK here!
}
catch (...)
{
// do something here (log the error, etc)...
// 'return' is illegal here!
}
};

Notice how the try is before the initialization list, and the body of the constructor is inside of the try block.

If checkValue() throws an exception, the construction of A is aborted, automatically destructing str in the process.

You could accomplish the same thing without using a function-try-block:

int checkValue(int value)
{
if (value == 1)
throw std::runtime_error("illegal value");
return value;
}

struct A
{
std::string str;
int value;

A(int i)
{
try
{
str = "hello";
value = checkValue(i);
// 'return' is OK here!
}
catch (...)
{
// do something here (log the error, etc)...
// 'return' is OK here! But then the constructor
// would not be aborted...
throw;
}
}
};

Where function-try-block is commonly used is when you want to catch/log exceptions that are thrown in things that cannot be invoked inside the normal constructor body, only in the initialization list, such as base class constructors and data member constructors.

Exception is caught in a constructor try block, and handled, but still gets rethrown a second time

It is a fundamental C++ core principle that an object is fully constructed only after the object's constructor returns. When execution enters the class's constructor, the class itself is not constructed yet. Only its class members are (and any base classes, too). Only when the constructor returns, only then the object is fully constructed, and you have a fully-cooked object to play with.

Therefore, a thrown exception in the constructor means that the object was not constructed. Full stop. End of story. That's all she wrote. No exceptions to this rule (pun intended).

Now, what does this mean, when you catch an exception that's thrown from the base class's constructor? Well, you can do that but it does not change the fact that an exception got thrown and the base class was not constructed. Which means that the derived class cannot be constructed. You can't expect the execution to reach the end of the exception handler, and then return to the parent, as if the class has been fully constructed without errors.

That is not just going to happen. You can't, somehow, end up with a base class not getting constructed, but the derived class getting constructed. C++ does not work this way.

When you catch an exception from the base class, you have only two options:

1) Your exception handler can rethrow its own exception, after it does whatever it wants to do.

2) Otherwise, C++ will rethrow the exception for you, if the exception handler returns without throwing its own exception.

How to catch a base class constructor's exception in C++?

There is a pretty good example of this here: Exception is caught in a constructor try block, and handled, but still gets rethrown a second time

Ultimately, if you manage to catch an exception in the derived class the only thing you should do is to either rethrow that exception or throw a new one. This is because if the base class constructor does not complete then the derived class will be in an undefined state and you should not use it.



Related Topics



Leave a reply



Submit