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
C++ Class Member Function Pointer to Function Pointer
Accessing Protected Members of Superclass in C++ with Templates
What Destructors Are Run When the Constructor Throws an Exception
Segmentation Fault at Glgenvertexarrays( 1, &Vao );
How to Convert Unsigned Char* to Std::String in C++
Casting via Void* Instead of Using Reinterpret_Cast
What Is the Meaning of Clang's -Wweak-Vtables
Handling Header Files Dependencies with Cmake
Can Openssl on Windows Use the System Certificate Store
Can't Overload Operator<< as Member Function
How to Change the Variable to Which a C++ Reference Refers
Why Is Std::Fill(0) Slower Than Std::Fill(1)
Difference Between Long and Int Data Types
C++ - Arguments for Exceptions Over Return Codes
Get the Status of a Std::Future