Exception Specifications
Whether a function throws an exception or not should really be part of its interface.
So, a function declaration may specify a set of exceptions that might be thrown by this function:
void myFunction(int myParam)
throw (exc1, exc2, exc3);
This specifies that myFunction may throw exceptions exc1, exc2, and exc3 (and exceptions derived from these), but not others.
When a function specifies something about its exceptions, it is effectively making a guarantee to its caller. If during execution, something happens to violate that guarantee, then the attempt will be transformed into a call to unexpected().
By default, unexpected() calls terminate() which, by default, calls abort().
A function declared with no exception specification is assumed to potentially throw any exception:
int myFunction(); // can throw any exception
This is consistent with C (and earlier C++) usage.
A function may be declared as throwing no exceptions:
int myOtherFunction() throw (); // throws no exceptions
Compile-time Checking of Exception Specifications
Some compile-time checking is done with exception specifications:
//
// ExcSpecs.h
// Exception Specifications
//
// Created by Bryan Higgs on 10/5/24.
//
#ifndef ExcSpecs_h
#define ExcSpecs_h
class SomethingBadHappened {};
void doSomething() throw (SomethingBadHappened);
void doSomethingElse() throw();
#endif /* ExcSpecs_h *///
// ExcSpecs.cpp
// Exception Specifications
//
// Created by Bryan Higgs on 10/5/24.
//
#include "ExcSpecs.h"
void doSomething() throw (SomethingBadHappened)
{
// Does not throw the specified exception
}
void doSomethingElse() // doesn't match declaration
{
// Throws an exception not specified
throw SomethingBadHappened();
}This produces a compile-time error:
ExcSpecs.cpp:15:13 'doSomethingElse' is missing exception specification 'throw()'
So, we fix it:
//
// ExcSpecs.h
// Exception Specifications
//
// Created by Bryan Higgs on 10/5/24.
//
#ifndef ExcSpecs_h
#define ExcSpecs_h
class SomethingBadHappened {};
void doSomething() throw (SomethingBadHappened);
void doSomethingElse() throw();
#endif /* ExcSpecs_h *///
// ExcSpecs.cpp
// Exception Specifications
//
// Created by Bryan Higgs on 10/5/24.
//
#include "ExcSpecs.h"
void doSomething() throw (SomethingBadHappened)
{
// Does not throw the specified exception
}
void doSomethingElse() throw() // Now matches declaration
{
// Throws an exception not specified
throw SomethingBadHappened();
}But it now produces a compile-time warning:
ExcSpecs.cpp:18:3 'doSomethingElse' has a non-throwing exception specification but can still throw
So, we fix that, by adding an exception specification to the doSomethingElse declaration and definition:
//
// ExcSpecs.h
// Exception Specifications
//
// Created by Bryan Higgs on 10/5/24.
//
#ifndef ExcSpecs_h
#define ExcSpecs_h
class SomethingBadHappened {};
void doSomething() throw (SomethingBadHappened);
void doSomethingElse() throw (SomethingBadHappened);
#endif /* ExcSpecs_h *///
// ExcSpecs.cpp
// Exception Specifications
//
// Created by Bryan Higgs on 10/5/24.
//
#include "ExcSpecs.h"
void doSomething() throw (SomethingBadHappened)
{
// Does not throw the specified exception
}
void doSomethingElse() throw (SomethingBadHappened)
{
// Throws a specified exception
throw SomethingBadHappened();
}Which, when combined with this main program…
//
// main.cpp
// Exception Specifications
//
// Created by Bryan Higgs on 10/5/24.
//
#include <iostream>
#include "ExcSpecs.h"
int main(int argc, const char * argv[])
{
try
{
doSomething();
doSomethingElse();
}
catch (SomethingBadHappened)
{
std::cout << "Caught SomethingBadHappened exception\n";
}
return 0;
}… produces the following:
Caught SomethingBadHappened exception
Program ended with exit code: 0
In other words, it works as expected.
But that was with the compiler set to use C++11. But once we switch to C++17, we get:
//
// ExcSpecs.h
// Exception Specifications
//
// Created by Bryan Higgs on 10/5/24.
//
#ifndef ExcSpecs_h
#define ExcSpecs_h
class SomethingBadHappened {};
void doSomething() throw (SomethingBadHappened);
void doSomethingElse() throw (SomethingBadHappened);
#endif /* ExcSpecs_h *///
// ExcSpecs.cpp
// Exception Specifications
//
// Created by Bryan Higgs on 10/5/24.
//
#include "ExcSpecs.h"
void doSomething() throw (SomethingBadHappened)
{
// Does not throw the specified exception
}
void doSomethingElse() throw (SomethingBadHappened)
{
// Throws a specified exception
throw SomethingBadHappened();
}Now it produces errors:
ExcSpecs.h:13:20 ISO C++17 does not allow dynamic exception specifications
ExcSpecs.h:15:24 ISO C++17 does not allow dynamic exception specifications
ExcSpecs.cpp:10:20 ISO C++17 does not allow dynamic exception specifications
ExcSpecs.cpp:15:24 ISO C++17 does not allow dynamic exception specifications
Why? Because Exception Specifications have been deprecated.
Warning:
C++ exception specifications, specifically the
throw(...)syntax, were deprecated in C++11 and completely removed in C++17. Here’s why:Problems with Exception Specifications:
- Runtime Overhead: Enforcing exception specifications added runtime overhead, which could be significant in performance-critical applications.
- Fragile: Exception specifications were fragile. If a function in a library changed its exception-throwing behavior, it could break code that relied on the original specification.
- Incomplete: Exception specifications couldn’t fully express the complex ways exceptions could propagate in a program. For example, a function might throw an exception that was not explicitly listed in its specification, or it might throw an exception from a nested function call.
- Unexpected Behavior: If a function threw an exception not listed in its specification, the
std::unexpected()function was called, which by default terminated the program. This could lead to unexpected crashes and made debugging difficult.
So what do we do?
- We can no longer specify that a function throws a set of exceptions.
- If we wish to specify that a function does not throw any exceptions, we can use some new syntax:
noexcept.
Why?
Here’s why:
- C++11 and later:
noexceptis the preferred way to specify that a function does not throw exceptions in modern C++.- Benefits: Using
noexceptallows the compiler to perform optimizations, potentially making your code more efficient. It also provides a clear indication to users of your function that it won’t throw exceptions.- Consequences of Throwing: If a function marked
noexceptdoes throw an exception, the program will terminate by callingstd::terminate().It’s a mess!
noexcept and the noexcept() operator
Here’s an example of noexcept usage:
//
// NoExcept.cpp
// Exception Specifications
//
// Created by Bryan Higgs on 10/5/24.
//
#include <iostream>
void print_message(const std::string& msg) noexcept
{
std::cout << msg << std::boolalpha <<
": I am a noexcept function: " << noexcept(print_message) << "\n";
}
int main()
{
print_message("Hello from print_message!");
return 0;
}… which outputs:
Hello from print_message!: I am a noexcept function: true
Program ended with exit code: 0
Note the existence of the noexcept() operator:
noexcept( expression )
It performs a compile-time check that returns true if an expression is declared to not throw any exceptions.