Exception specifications were introduced in C++98 and replaced by noexcept in C++11. Exception specifications serve to restrict the exceptions thrown by functions, enhancing program readability.However, if a function throws an exception that is not allowed by its exception specification, the program will call the unexpected function, which by default calls terminate, causing the program to stop immediately. This necessitates extreme caution when using exception specifications, especially when the libraries in use do not adhere to exception specifications.Since exception specifications are checked locally, when a function with an exception specification calls other functions, the compiler will not report an error even if those other functions may throw exceptions that violate the exception specification:
// Function B may throw an exceptionvoid B();// Function A's exception specification does not throw any exceptionsvoid A() throw() { B(); // A calls B, which may violate the exception specification}
At this point, using function A could lead to program termination at any time. To avoid this issue, the following three rules are proposed:1. Do not combine templates with exception specifications
template<class T>void copy(T& t1, const T& t2) throw() { t1 = t2;}
We have written a template function that calls the assignment operator of the object to perform a copy operation, and the exception specification states that it does not throw any exceptions.It seems fine, but if we pass in an assignment operator that has been overridden and may throw an exception, then this template function could cause the program to terminate.Therefore, templates and exception specifications should not be combined, as we cannot be sure whether the members of the passed-in object will lead to exceptions.2. Be cautious of exceptions caused by callback functionsCallback functions allow us to typecast function pointers, enabling us to customize the function that the pointer points to and call it through the function pointer.
// Define function pointer typetypedef void (*FunPtr)(int a);// Create a function pointerFunPtr funPtr;// Other functions use the function through the pointervoid Fun() throw() { funPtr(a);}
funPtr could point to any function of the same type, and we cannot determine whether it is a function that may throw an exception. Therefore, function Fun could lead to program termination at any time.To avoid this situation, functions with exception specifications should avoid using callback functions. If it is necessary to use them, the function pointer type can be given an exception specification (some compilers may not support this):
// Define function pointer type with exception specificationtypedef void (*FunPtr)(int a) throw();// An error will occur when funPtr points to a pointer without the throw() exception specificationFunPtr funPtr;
3. Be cautious of exceptions from libraries and the systemWhen using libraries and C++ built-in functions and operators, they may also throw exceptions. It is clearly unreasonable to wrap every statement in a try-catch block to prevent this.Therefore, to handle such errors, we can directly modify the unexpected function that is called when an exception specification is violated:
// Define an exception classclass Unex {};// Redefine the unexpected functionvoid newUnexpected() { // Throw a unified exception throw Unex();}// Set unexpected to newUnexpectedunexpected(newUnexpected);
At this point, any exception thrown by libraries and C++ built-in functions and operators will call newUnexpected, turning it into a unified Unex exception. We only need to add Unex to the exception specification.If we do not want to create the Unex exception, we can do it this way:
// Redefine the unexpected functionvoid newUnexpected() { // Throw a unified exception throw;}// Set unexpected to newUnexpectedunexpected(newUnexpected);
At this point, newUnexpected will convert any exception into a bad_exception object, effectively using bad_exception instead of Unex.