When Is a Function Try Block Useful

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 (...) { ... } }

What is the purpose of a function try block?

Imagine if UseResources was defined like this:

class UseResources
{
class Cat *cat;
class Dog dog;

public:
UseResources() : cat(new Cat), dog() { cout << "UseResources()" << endl; }
~UseResources() { delete cat; cat = NULL; cout << "~UseResources()" << endl; }
};

If Dog::Dog() throws, then cat will leak memory. Becase UseResources's constructor never completed, the object was never fully constructed. And therefore it does not have its destructor called.

To prevent this leak, you must use a function-level try/catch block:

UseResources() try : cat(new Cat), dog() { cout << "UseResources()" << endl; } catch(...)
{
delete cat;
throw;
}

To answer your question more fully, the purpose of a function-level try/catch block in constructors is specifically to do this kind of cleanup. Function-level try/catch blocks cannot swallow exceptions (regular ones can). If they catch something, they will throw it again when they reach the end of the catch block, unless you rethrow it explicitly with throw. You can transform one type of exception into another, but you can't just swallow it and keep going like it didn't happen.

This is another reason why values and smart pointers should be used instead of naked pointers, even as class members. Because, as in your case, if you just have member values instead of pointers, you don't have to do this. It's the use of a naked pointer (or other form of resource not managed in a RAII object) that forces this kind of thing.

Note that this is pretty much the only legitimate use of function try/catch blocks.


More reasons not to use function try blocks. The above code is subtly broken. Consider this:

class Cat
{
public:
Cat() {throw "oops";}
};

So, what happens in UseResources's constructor? Well, the expression new Cat will throw, obviously. But that means that cat never got initialized. Which means that delete cat will yield undefined behavior.

You might try to correct this by using a complex lambda instead of just new Cat:

UseResources() try
: cat([]() -> Cat* try{ return new Cat;}catch(...) {return nullptr;} }())
, dog()
{ cout << "UseResources()" << endl; }
catch(...)
{
delete cat;
throw;
}

That theoretically fixes the problem, but it breaks an assumed invariant of UseResources. Namely, that UseResources::cat will at all times be a valid pointer. If that is indeed an invariant of UseResources, then this code will fail because it permits the construction of UseResources in spite of the exception.

Basically, there is no way to make this code safe unless new Cat is noexcept (either explicitly or implicitly).

By contrast, this always works:

class UseResources
{
unique_ptr<Cat> cat;
Dog dog;

public:
UseResources() : cat(new Cat), dog() { cout << "UseResources()" << endl; }
~UseResources() { cout << "~UseResources()" << endl; }
};

In short, look on a function-level try-block as a serious code smell.

How to use correctly Constructor-Function-Try-Block?

I didn't understand the second and third paragraphs; it looks to me like it is contradictory, because it is said that to handle exceptions from a constructor-init list we use ctor-function-try block, and from the other side it said that "it's worth noting that an exception... such exceptions are not a part from function-try-block...".

Those "..."s are important. The actual quote was:

an exception can happen while initializing the constructor's parameters

Emphasis added. That is, the paragraph is about the initialization of parameters before calling the constructor.



And now the exception in not handled and std::terminate is called?!

Precisely. This behavior has nothing to do with the text you quoted. This happens because function-level try blocks on constructors cannot swallow exceptions.

If a member of an object fails to be constructed, the object itself has failed to be constructed. Members of an object are not optional (well, except for std::optional ones ;) ); they must be properly constructed. Employee's constructor failed to initialize Employee::age_. The function catch block was called.

But the Employee object still failed to be constructed. There is only one way in C++ to communicate that an object has failed to be constructed: throwing an exception. And C++ just so happens to have an exception right there in the constructor's function try block.

As such, when a constructor's function-level catch block exits, the exception is always rethrown, exactly as if you had issued a throw e; at the end of the block.

The only thing a constructor's function-level try/catch block can do is cleanup. They cannot stop the propagation of an exception. Reasonable use of RAII makes constructor function try/catch blocks unnecessary in nearly all cases.

Is a function-try-block equivalent to a try-catch encompassing whole function?

Yes, but not for constructor bodies.

That's why function-try-block was invented:

The primary purpose of function-try-blocks is to respond to an exception thrown from the member initializer list in a constructor by logging and rethrowing, modifying the exception object and rethrowing, throwing a different exception instead, or terminating the program.

Side note: thread interruption

Boost's thread interruption mechanism is cooperative, not asynchronous (like POSIX signals). That means that, no between { and try { there cannot be an interruption:

  • https://www.boost.org/doc/libs/1_54_0/doc/html/thread/thread_management.html#thread.thread_management.this_thread.interruption_point

Even if were fully asynchronous, then still it would not make any sense to reason about the "difference" because there would not be any happens-before relationship anyways, so both outcomes could occur in both situations anyways (it's timing dependent regardless).

Why wouldn't you declare main() using a function-try-block?

You can easily convert

int main() try {
// The real code of main
}
catch (...)
{
}

to

int realMain()
{
// The real code of main
}

int main()
{
try
{
return realMain();
}
catch ( ... )
{
}
}

without losing functionality/behavior.

I am going to guess that whether you use the first version or the second version is a matter of coding practices of a team. From a compiler and run time standpoint, I don't see any semantic difference.

Function try blocks, but not in constructors

Function try blocks are only ever needed in constructors. In all other cases exactly the same effect can be achieved by enclosing the entire body of the function in a normal try/catch block.

If the copy constructor used to initialize a parameter throws an exception this happens before the function call. It cannot be caught by a function try block or exceptional handler in the function as the function doesn't get called.

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.

Why there is no function-try-block for lambda?

Originally, function-try-blocks were introduced to be able to catch exceptions thrown in constructors or destructors of subobjects. The syntax was extended to normal functions for consistency.

It would, of course, be possible to introduce such syntax for lambdas. However, as opposed to constructors and destructors, there is no practical advantage over simply enclosing the try-block in another pair of { }, except the latter looks much less obscure.



Related Topics



Leave a reply



Submit