Try and catch blocks work well enough in most cases, but there is one particular case in which they are not sufficient. Consider the following example:
In the above example, derived class B calls base class constructor A, which can throw an exception. Because the creation of object b has been placed inside a try block (in function main()), if A throws an exception, main’s try block will catch it. Consequently, this program prints:
Oops
But what if we want to catch the exception inside of B? The call to base constructor A happens via the member initializer list, before the B constructor’s body is called. There’s no way to wrap a standard try block around it.
In this situation, we have to use a slightly modified try block called a function try block.
Function try blocks
Function try blocks are designed to allow you to establish an exception handler around the body of an entire function, rather than around a block of code.
The syntax for function try blocks is a little hard to describe, so we’ll show by example:
When this program is run, it produces the output:
Exception caught Oops
Let’s examine this program in more detail.
First, note the addition of the try
keyword before the member initializer list. This indicates that everything after that point (until the end of the function) should be considered inside of the try block.
Second, note that the associated catch block is at the same level of indentation as the entire function. Any exception thrown between the try keyword and the end of the function body will be eligible to be caught here.
When the above program runs, variable b
begins construction, which calls B’s constructor (which utilizes a function try). B’s constructor calls A’s constructor, which then raises an exception. Because A’s constructor does not handle this exception, the exception propagates up the stack to B’s constructor, where it is caught by the function-level catch of B’s constructor. The catch block prints “Exception caught”, and then rethrows the current exception up the stack, which is caught by the catch block in main()
, which prints “Oops”.
Best practice
Use function try blocks when you need a constructor to handle an exception thrown in the member initializer list.
Limitations on function catch blocks
With a regular catch block (inside a function), we have three options: We can throw a new exception, rethrow the current exception, or resolve the exception (by either a return statement, or by letting control reach the end of the catch block).
A function-level catch block for a constructor must either throw a new exception or rethrow the existing exception -- they are not allowed to resolve exceptions! Return statements are also not allowed, and reaching the end of the catch block will implicitly rethrow.
A function-level catch block for a destructor can throw, rethrow, or resolve the current exception via a return statement. Reaching the end of the catch block will implicitly rethrow.
A function-level catch block for other functions can throw, rethrow, or resolve the current exception via a return statement. Reaching the end of the catch block will implicitly resolve the exception for non-value (void) returning functions and produce undefined behavior for value-returning functions!
The following table summarizes the limitations and behavior of function-level catch blocks:
Function type | Can resolve exceptions via return statement |
Behavior at end of catch block |
---|---|---|
Constructor | No, must throw or rethrow | Implicit rethrow |
Destructor | Yes | Implicit rethrow |
Non-value returning function | Yes | Resolve exception |
Value-returning function | Yes | Undefined behavior |
Because such behavior at the end of the catch block varies dramatically depending on the type of function (and includes undefined behavior in the case of value-returning functions), we recommend never letting control reach the end of the catch block, and always explicitly throwing, rethrowing, or returning.
Best practice
Avoid letting control reach the end of a function-level catch block. Instead, explicitly throw, rethrow, or return.
In the program above, if we had not explicitly rethrow the exception in the function-level catch block of the constructor, control would have reached the end of the function-level catch, and because this was a constructor, an implicit rethrow would have happened instead. The result would have been the same.
Although function level try blocks can be used with non-member functions as well, they typically aren’t because there’s rarely a case where this would be needed. They are almost exclusively used with constructors!
Function try blocks can catch both base and the current class exceptions
In the above example, if either A or B’s constructor throws an exception, it will be caught by the try block around B’s constructor.
We can see that in the following example, where we’re throwing an exception from class B instead of class A:
We get the same output:
Exception caught Oops
Don’t use function try to clean up resources
When construction of an object fails, the destructor of the class is not called. Consequently, you may be tempted to use a function try block as a way to clean up a class that had partially allocated resources before failing. However, referring to members of the failed object is considered undefined behavior since the object is “dead” before the catch block executes. This means that you can’t use function try to clean up after a class. If you want to clean up after a class, follow the standard rules for cleaning up classes that throw exceptions (see the “When constructors fail” subsection of lesson 27.5 -- Exceptions, classes, and inheritance).
Function try is useful primarily for either logging failures before passing the exception up the stack, or for changing the type of exception thrown.
Hello Alex,
I’d like to get your input from a design perspective on the idea of implementing a centralized exception handling mechanism, potentially as a small, pluggable utility or library.
Background:
In our current setup, we have several class functions that call C++ APIs, which can throw exceptions. To handle these exceptions, we typically wrap each function call in individual try-catch blocks. While this ensures proper error handling, it leads to significant code bloat and reduces readability and maintainability across the codebase.
Proposal:
Instead of scattering try-catch logic throughout the code, we are considering designing a centralized exception handling utility. This could be a lightweight and reusable module that encapsulates exception handling logic, making it easier to manage, extend, and consistently log or respond to exceptions in one place.
Questions:
From a design and architectural standpoint, does this approach seem sound?
Are there any potential drawbacks or patterns we should be careful of when abstracting exception handling this way?
Would it be more idiomatic in C++ to use higher-level wrappers or RAII techniques for this purpose?
Looking forward to your thoughts.
Thanks in Advance,
Sundeep
@Alex:
Hello Alex,
Request to provide your thoughts on the above
Thanks and Regards
Sundeep
What is it supposed to happen if an exception occurs in initializer list:
https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/n4950.pdf
14.1
The catch blocks associated with the function try block will try to catch the exception. Whether they succeed or not, the object is not considered constructed. This just gives you an opportunity to intercept the exception before it propagates back to whomever is creating the A object.
Let’s examine this program in more detail.
First, note the addition of the “try” keyword before the member initializer list. This indicates that everything after that point (until the end of the function) should be considered inside of the try block.
Second, note that the associated catch block is at the same level of indentation as the entire function. Any exception thrown between the try keyword and the end of the *function * body will be eligible to be caught here.
Hello Alex, What do you mean by 'function' in those paragraphs? Do you mean constructor?
Yes, constructors are functions (as are destructors).
"But what if we want to catch the exception inside of B? The call to base constructor A happens via the member initialization list, before the B constructor’s body is called. There’s no way to wrap a standard try block around it."
Well, I tried...
Not only it is ugly but also indeed fails to compile. But I still don't understand why it is not possible
It's not possible because that's not the syntax they decided on for doing function-level try. Probably because it makes the function hard to find.
function try blocks don't work with lambdas :(
Small typo here
> Avoid letting control each the end of a function-level catch block. Instead, explicitly throw, rethrow, or return.
Should presumably be reach.
Side note, this guide has helped me progress from knowing nearly nothing about coding to here. Cheers and thank you for all your work.
The missing 'r' has been located and returned to its intended position. Thanks!
> with function-level try blocks, you must throw or rethrow an exception
Only in constructors and destructors
And in value returning functions (otherwise UB will result). I've updated the lesson a bit to explicitly note that the catch blocks of non-value returning functions can resolve exceptions, but everyone else must throw or rethrow (either explicitly or implicitly), and added a table with behavioral summary info. Thanks for pointing out the issue!
> And in value returning functions (otherwise UB will result)
Values can be returned from function-try-block catch clauses:
Destructors can return without rethrowing too (rethrowing is implicit when they don't). See https://en.cppreference.com/w/cpp/language/function-try-block
That's what I get for trying to rewrite a significant portion of a lesson while sick. :/ Thanks for the corrections!
Ah, that sucks. Hope you get well soon :)
The new section is great. I really like the idea of using a table there
> see the “When constructor fail” subsection of lesson
*"When constructors fail" (I checked)
… if either A or B’s constructor throw an exception …
(throws
, I assume)#include <iostream>
is missing in the first example.