Function Try Blocks

Learn about Function Try Blocks, and their importance in managing exceptions in constructors
This lesson is part of the course:

Professional C++

Comprehensive course covering advanced concepts, and how to use them on large-scale projects.

Free, Unlimited Access
Abstract art representing computer programming
Ryan McCombe
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.

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.

Was this lesson useful?

Next Lesson

Static Arrays using std::array

An introduction to static arrays using std::array - an object that can store a collection of other objects
Abstract art representing computer programming
Ryan McCombe
Ryan McCombe
Updated
Lesson Contents

Function Try Blocks

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

A computer programmer
This lesson is part of the course:

Professional C++

Comprehensive course covering advanced concepts, and how to use them on large-scale projects.

Free, Unlimited Access
Exceptions and Error Handling
A computer programmer
This lesson is part of the course:

Professional C++

Comprehensive course covering advanced concepts, and how to use them on large-scale projects.

Free, unlimited access

This course includes:

  • 125 Lessons
  • 550+ Code Samples
  • 96% Positive Reviews
  • Regularly Updated
  • Help and FAQ
Next Lesson

Static Arrays using std::array

An introduction to static arrays using std::array - an object that can store a collection of other objects
Abstract art representing computer programming
Contact|Privacy Policy|Terms of Use
Copyright © 2024 - All Rights Reserved