Function-try-block is a mechanism in C++ to establish an exception handler around the body of a function. The following is an example:

int foo() { throw std::runtime_error("oops..."); } int main() try { foo(); return 0; } catch (...) { return -1; } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 int foo ( ) { throw std : : runtime_error ( "oops..." ) ; } int main ( ) try { foo ( ) ; return 0 ; } catch ( . . . ) { return - 1 ; }

The function foo() throws and the exception is caught in the catch block so that the function main() returns with value -1.

Function-try-block can be used with regular functions, constructors, and destructors. Their use for functions and destructors is of little use and so far I have never seen it used in these cases. The function main() above is semantically equivalent to the following:

int main() { try { foo(); return 0; } catch (...) { return -1; } } 1 2 3 4 5 6 7 8 9 10 11 12 int main ( ) { try { foo ( ) ; return 0 ; } catch ( . . . ) { return - 1 ; } }

However, there are still gotchas that you must be aware of:

Exceptions from constructors and destructors of objects declared in the global namespace are not caught with function-try-catch in main() . struct foo { foo() { throw std::runtime_error("oops..."); } }; foo f; int main() try { return 0; } catch (...) { return -1; } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 struct foo { foo ( ) { throw std : : runtime_error ( "oops..." ) ; } } ; foo f ; int main ( ) try { return 0 ; } catch ( . . . ) { return - 1 ; }

. Exceptions from destructors of static objects are not caught with function-try-catch in main() . struct foo { ~foo() noexcept(false) { throw std::runtime_error("oops..."); } }; int main() try { static foo f; return 0; } catch (...) { return -1; } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 struct foo { ~ foo ( ) noexcept ( false ) { throw std : : runtime_error ( "oops..." ) ; } } ; int main ( ) try { static foo f ; return 0 ; } catch ( . . . ) { return - 1 ; }

. If a function (any function, not just main() ) has a return type other than void and the function-try-catch does not have a return statement in the catch block then the behaviour is undefined. int foo() { throw std::runtime_error("oops..."); } int main() try { return 0; } catch (...) { // undefined behaviour } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 int foo ( ) { throw std : : runtime_error ( "oops..." ) ; } int main ( ) try { return 0 ; } catch ( . . . ) { // undefined behaviour }

So what is then the real use of function-try-catch? The answer is catching exceptions in constructors member initializer list. Let’s take the following example:

int foo() { throw std::runtime_error("oops..."); } struct bar { bar() try : data(foo()) { } catch (std::exception const & e) { std::cout << e.what() << '

'; } private: int data; }; int main() { bar b; } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 int foo ( ) { throw std : : runtime_error ( "oops..." ) ; } struct bar { bar ( ) try : data ( foo ( ) ) { } catch ( std : : exception const & e) { std::cout << e.what() << '

'; } private : int data ; } ; int main ( ) { bar b ; }

A function-try-block is used with the constructor of bar . The exception thrown by the function foo() is caught in the catch block. If you run the program, “oops…” is printed to the console. But then, the program is aborted. The reason for this that the exception caught in the catch block of a function-try-catch of a constructor or destructor is rethrown! Therefore, the purpose of this handler is to perhaps log the error and/or run some cleanup code. Alternatively, you can throw a different exception than the one being caught, as shown in the following example:

struct bar { bar() try : data(foo()) { } catch (std::exception const & e) { std::cout << e.what() << '

'; throw std::runtime_error("bar failed to initialize"); } private: int data; }; int main() { try { bar b; } catch (std::exception const & e) { std::cout << e.what() << '

'; } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 struct bar { bar ( ) try : data ( foo ( ) ) { } catch ( std : : exception const & e) { std::cout << e.what() << '

'; throw std : : runtime_error ( "bar failed to initialize" ) ; } private : int data ; } ; int main ( ) { try { bar b ; } catch ( std : : exception const & e) { std::cout << e.what() << '

'; } }

Of course, you can have multiple catch blocks too:

struct bar { bar() try : data(foo()) { } catch (std::runtime_error const & e) { std::cout << e.what() << '

'; } catch (std::exception const & e) { std::cout << "unexpected: " << e.what() << '

'; } private: int data; }; 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 struct bar { bar ( ) try : data ( foo ( ) ) { } catch ( std : : runtime_error const & e) { std::cout << e.what() << '

'; } catch ( std : : exception const & e) { std::cout << "unexpected: " << e.what() << '

'; } private : int data ; } ;

It is important to note that base class destructors are called after derived class destructors but before the catch block of function-try-block of the derived destructor.

int foo() { throw std::runtime_error("oops..."); } struct base { virtual ~base() noexcept(false) { std::cout << "destructing base" << '

'; } }; struct derived : base { ~derived() noexcept(false) try { std::cout << "destructing derived" << '

'; foo(); } catch (std::exception const & e) { std::cout << "got it: " << e.what() << '

'; } }; int main() { try { derived d; } catch (std::exception const & e) { std::cout << e.what() << '

'; } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 int foo ( ) { throw std : : runtime_error ( "oops..." ) ; } struct base { virtual ~ base ( ) noexcept ( false ) { std : : cout < < "destructing base" < < '

' ; } } ; struct derived : base { ~ derived ( ) noexcept ( false ) try { std : : cout < < "destructing derived" < < '

' ; foo ( ) ; } catch ( std : : exception const & e) { std::cout << "got it: " << e.what() << '

'; } } ; int main ( ) { try { derived d ; } catch ( std : : exception const & e) { std::cout << e.what() << '

'; } }

The output of this program is:

destructing derived destructing base got it: oops... oops... 1 2 3 4 destructing derived destructing base got it : oops . . . oops . . .

Further readings:

Note: whether C++ destructors should throw or not is another topic beyond the scope of this article.

Share this: Facebook

Twitter

Print

More

Email

Reddit



