C++ Exceptions

C++ Exceptions

Tags
C++
Programming
Published
October 4, 2021
Author
yanbc

What are exceptions?

  • Exception was a new error handling pattern that was introduced to C++ at the beginning of the language (C++98) and has been evolving ever since.
  • The basic code structure for using exception involves three parts
    • One try block
    • One or more catch blocks
    • One or more throw statements
  • It is best to look at an example first exception_demo.cpp
    • #include <iostream> using namespace std; int main() { try { throw 20; } catch (int e) { cout << "An exception occurred. Exception #" << e << '\n'; } return 0; }
  • Note that the catch (int e) statement looks a lot like function definition that, which in this case, takes an integer as an argument.
  • In this sense, throw looks a lot like return as well. The throw statement gives control back to anther function and with it, a variable gets passed.
  • For built-in types like int and char, it is common to pass them by value. For more complex types such as class objects, it is generally more suitable to pass them by reference.
  • Here is another example throwing objects exception_ojbect.cpp
    • #include <iostream> #include <exception> #include <string> using namespace std; class MyException : public exception { private: string value; public: MyException(int v) { value = to_string(v); } const char *what() { return value.c_str(); } }; int main() { try { throw MyException(20); } catch (MyException &e) { cout << "An exception occurred. Exception Nr. " << e.what() << '\n'; } return 0; }
  • Even though we are catching a reference to an object of the MyException type, it is actually a copy of the original object being thrown.
  • The reason to use a reference is that, a base-class reference can also refer to derived-class objects. Say you have a collection of exception objects, each is thrown at different circumstances. You can then catch them all with a reference to the base exception type.
    • class BaseException { ... }; class Case1Exception : public BaseException { ... }; class Case2Exception : public BaseException { ... }; void my_function() { ... if (case1) throw Case1Exception(); if (case2) throw Case2Exception(); ... } int main() { ... try: my_function(); catch (BaseException &be) { ... } .... return 0; }

Exceptions specification in functions

Remember when I said the throw statement looks a lot like return? Wouldn’t it be nice to include the exceptions that a function might throw in the signature, along with the return types and input variables?
Well, the good news is that it did for the early version of C++ and the official name of this feature is called exception specification. It allows function to use the throw() notation to specify the expected exception types. The bad news is that they removed it later. To be precise, the use of exceptions specification was included in C++98 and C++03, deprecated in C++11 and completely removed in C++17.
Here is an example. You can compile it with c++ -o exception_specification -std=c++98 exception_specification.cpp. Note the function signature void foo() throw(int). It says that the function foo() takes void as input, return void as output, and might throw an int exception.
#include <iostream> // A function with an exception specification using throw() void foo() throw(int) { int x = 0; if (x == 0) { throw 42; // This is allowed because we specified throw(int) } } int main() { try { foo(); } catch (int e) { std::cerr << "Caught an exception: " << e << std::endl; } return 0; }
exception_specification.cpp
The main reason why the committee decided to remove exception specification is that it is ignored by the programming community. Yes, it looks really nice and promising at first sight but if you think about it, it is useless.
Say you are working on a function that relies on a third-party library. You might be able to sort things out and specifies all the exceptions your function might throw for the this version of the library. But libraries get updated, and new exceptions are introduced. You then would have to rewrite the function to adapt to the changes. It is cumbersome and programmers are often lazy. So what will you do? You would opt not to specify the allowed exceptions in the first place.
With that said, the noexcept specifier did continue to exist in modern C++. It allows programmers to specify whether a function is allowed to throw an exception or not.
void foo() noexcept; // foo will not throw any exception void bar() noexcept(false); // bar is allowed to throw an exception

Altering the default behavior

Working with dynamic memory allocation

References