Exception Specifications

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: noexcept is the preferred way to specify that a function does not throw exceptions in modern C++.
  • Benefits: Using noexcept allows 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 noexcept does throw an exception, the program will terminate by calling std::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.

Index