Function Try Blocks

Learn about Function Try Blocks, and their importance in managing exceptions in constructors

Ryan McCombe
Updated

Previously, we have seen how we can have try and catch blocks inside a function:

#include <iostream>
#include <stdexcept>

void MyFunction() {
  try {
    throw std::runtime_error { "Oops!" };
  } catch (std::runtime_error& e) {
    std::cout << "Error: " << e.what();
  }
}

int main() {
  MyFunction();
}
Error: Oops!

A function try block is a special type of try-catch block that is used to catch exceptions thrown anywhere within the function that it is applied to. In this article, we will explain the basics of function try blocks, and when they're useful.

Using Function Try Blocks

There is an alternative syntax for this, where we want the try and catch to apply to our whole function. It looks like this:

#include <iostream>
#include <stdexcept>

void MyFunction() try {
  throw std::runtime_error { "Oops!" };
} catch (std::runtime_error& e) {
  std::cout << e.what();
}

int main() {
  MyFunction();
}
Error: Oops!

As with regular try-catch blocks, we can have multiple catch statements, to catch different types of errors thrown within our function body:

#include <iostream>
#include <stdexcept>

void MyFunction() try {
  throw std::runtime_error { "Oops!" };
} catch (std::logic_error& e) {
  std::cout << "Logic error!";
} catch (std::runtime_error& e) {
  std::cout << "Runtime error!";
}

int main() {
  MyFunction();
}
Runtime error!

As usual, the exception we're catching doesn't need to be directly thrown within the associated body. We can catch exceptions that bubbled up the call stack:

#include <iostream>
#include <stdexcept>

void ThrowException() {
  throw std::runtime_error{"Oops!"};
}

void MyFunction() try {
  ThrowException();
} catch (std::runtime_error& e) {
  std::cout << "Runtime error!";
}

int main() {
  MyFunction();
}
Runtime error!

Using Function Try Blocks with Return Types

Where the function has a non-void return type, all of the catch statements will also need to respect that, by returning an appropriate object:

#include <exception>

int Divide(int x, int y) try {
  if (y == 0) {
    throw std::invalid_argument{
      "Cannot divide by zero"};
  }
  return x/y;  
} catch (std::invalid_argument& e) {
  return -1;  
}

Exceptions in Member Initializer Lists

Naturally, we can get the same behavior of a function try block by simply having the entire body of the function be a try-catch block.

So in the previous examples, the function try block isn't helping us achieve anything we couldn't before - although it could be argued that the syntax is more readable.

However, some exceptions can only be caught by a function try block: specifically, exceptions that occur in member initializer lists.

Member Initializer Lists

This lesson introduces Member Initializer Lists, focusing on their advantages for performance and readability, and how to use them effectively

In the following example, the Enemy constructor is going to receive an exception. However, the exception is coming from the member initializer list, outside of the function body:

#include <iostream>
#include <stdexcept>

int GetHealth(int Health) {
  if (Health < 0) throw std::logic_error{
    "Health cannot be negative"};

  return Health;
}

class Enemy {
public:
  Enemy(int Health)
    : mHealth{GetHealth(Health)} {}

 private:
  int mHealth;
};

int main() {
  Enemy Goblin { -100 };
}
terminate called after throwing an instance of std::logic_error

When a member initializer list throws an exception, a function try block is the only way to catch that exception:

#include <iostream>
#include <stdexcept>

int GetHeath(int Health) {/*...*/} class Enemy { public: Enemy(int Health) try : mHealth{GetHealth(Health)} { // ... Constructor body } catch (std::logic_error& e) { std::cout << e.what(); } private: int mHealth; }; int main() { Enemy Goblin { -100 }; }

We now get our custom error message:

Health cannot be negative
terminate called after throwing an instance of 'std::logic_error'

Handling Member Initializer List Exceptions

The previous output shows that our program is still terminated, even though we caught the exception.

When an exception is thrown as part of a constructor's function-try block, it is impossible to recover from that within the associated catch blocks.

We need to re-throw the exception, or throw a different exception. If we don't rethrow the exception, it will be implicitly rethrown for us.

Either way, an exception escapes from the constructor, and needs to be handled elsewhere in the stack:

#include <iostream>
#include <stdexcept>

int GetHeath(int Health) {/*...*/}
class Enemy {/*...*/} int main() { try { Enemy Goblin { -100 }; } catch (std::exception& e) { std::cout << "\nGoblin construction failed"; } std::cout << " but we recovered"; }
Health cannot be negative
Goblin construction failed but we recovered

Therefore, the use of function try blocks within constructors has two purposes:

  • To change the type of error thrown, by rethrowing a different type
  • For secondary effects - eg, reporting the error to some tracker

Summary

In this lesson, we explored the concept and application of function try blocks in C++. In particular, we emphasized their unique ability to handle exceptions from member initializer lists in constructors. The key takeaways include:

  • Function try blocks are special try-catch constructs that can catch exceptions thrown anywhere within a function, including member initializer lists.
  • In constructors, function try blocks are the only means to catch exceptions arising from member initializer lists.
  • When an exception is caught in a constructor's function-try block, it cannot be fully recovered within the catch block; it must be re-thrown or replaced with another exception.
  • Function try blocks in non-void functions require that all catch blocks respect the function's return type.
  • The primary uses of function try blocks in constructors are to modify the type of thrown exception and to perform secondary actions, like error logging.
Next Lesson
Lesson 42 of 126

Static Arrays using std::array

An introduction to static arrays using std::array - an object that can store a collection of other objects

Questions & Answers

Answers are generated by AI models and may not have been reviewed. Be mindful when running any code on your device.

Handling Constructor Exceptions
How can I handle exceptions thrown in a constructor?
Multiple Catch Blocks in Function Try Blocks
Can I have multiple catch blocks in a function try block?
Return Types in Function Try Blocks
How do I specify a return type for a function that uses a function try block?
Function Try Block vs Regular Try-Catch
When should I use a function try block instead of a regular try-catch block?
Rethrowing Exceptions
What happens if I don't rethrow an exception caught in a function try block?
Catching All Exceptions
How can I catch all exceptions in a function try block?
Or Ask your Own Question
Get an immediate answer to your specific question using our AI assistant