Should I Use an Exception Specifier in C++

Should I use an exception specifier in C++?

No.

Here are several examples why:

  1. Template code is impossible to write with exception specifications,

    template<class T>
    void f( T k )
    {
    T x( k );
    x.x();
    }

    The copies might throw, the parameter passing might throw, and x() might throw some unknown exception.

  2. Exception-specifications tend to prohibit extensibility.

    virtual void open() throw( FileNotFound );

    might evolve into

    virtual void open() throw( FileNotFound, SocketNotReady, InterprocessObjectNotImplemented, HardwareUnresponsive );

    You could really write that as

    throw( ... )

    The first is not extensible, the second is overambitious and the third is really what you mean, when you write virtual functions.

  3. Legacy code

    When you write code which relies on another library, you don't really know what it might do when something goes horribly wrong.

    int lib_f();

    void g() throw( k_too_small_exception )
    {
    int k = lib_f();
    if( k < 0 ) throw k_too_small_exception();
    }

    g will terminate, when lib_f() throws. This is (in most cases) not what you really want. std::terminate() should never be called. It is always better to let the application crash with an unhandled exception, from which you can retrieve a stack-trace, than to silently/violently die.

  4. Write code that returns common errors and throws on exceptional occasions.

    Error e = open( "bla.txt" );
    if( e == FileNotFound )
    MessageUser( "File bla.txt not found" );
    if( e == AccessDenied )
    MessageUser( "Failed to open bla.txt, because we don't have read rights ..." );
    if( e != Success )
    MessageUser( "Failed due to some other error, error code = " + itoa( e ) );

    try
    {
    std::vector<TObj> k( 1000 );
    // ...
    }
    catch( const bad_alloc& b )
    {
    MessageUser( "out of memory, exiting process" );
    throw;
    }

Nevertheless, when your library just throws your own exceptions, you can use exception specifications to state your intent.

Difference between C++03 throw() specifier C++11 noexcept

Exception specifiers were deprecated because exception specifiers are generally a terrible idea. noexcept was added because it's the one reasonably useful use of an exception specifier: knowing when a function won't throw an exception. Thus it becomes a binary choice: functions that will throw and functions that won't throw.

noexcept was added rather than just removing all throw specifiers other than throw() because noexcept is more powerful. noexcept can have a parameter which compile-time resolves into a boolean. If the boolean is true, then the noexcept sticks. If the boolean is false, then the noexcept doesn't stick and the function may throw.

Thus, you can do something like this:

struct<typename T>
{
void CreateOtherClass() { T t{}; }
};

Does CreateOtherClass throw exceptions? It might, if T's default constructor can. How do we tell? Like this:

struct<typename T>
{
void CreateOtherClass() noexcept(is_nothrow_default_constructible<T>::value) { T t{}; }
};

Thus, CreateOtherClass() will throw iff the given type's default constructor throws. This fixes one of the major problems with exception specifiers: their inability to propagate up the call stack.

You can't do this with throw().

Function exceptions specification and standard exceptions - foo() throw(Exception)

The simple "good practice" tip is: don't use exception specifications.

Essentially the only exception to that is the possibility of an empty exception specification: throw(). That's sufficiently useful that in C++11 it's been given its own keyword (noexcept). It's generally agreed that any non-empty exception specification is a lousy idea though.

Exception specifications (other than noexcept) are officially deprecated -- and unlike many deprecated features, removing this would affect little enough source code that I think there's a good chance it really will eventually be removed (certainly no guarantee, but a pretty fair chance anyway).

As for what happens when/if you do throw an exception of a type not allowed by the exception specification: std::unexpected() gets invoked. By default, that invokes terminate(). You can use std::set_unexpected to set your own handler -- but about all you can reasonably do is add some logging before you terminate(). Your unexpected handler is not allowed to return.

Why exception specifications cannot be useful?

Compiler-verified exceptions as part of a function's signature have two (theoretical) advantages: compiler optimizations and compile-time error checking.

What is the difference, in terms of the compiler, between a function that throws exception class X and class Y? Ultimately... nothing at all. What kind of optimization could the compiler do with exception class X that it couldn't do with Y? Unless std::exception were special (and X were derived from it, while Y was not), what would it matter to the compiler?

Ultimately, the only thing a compiler would care about in terms of optimization is whether a function will throw any exceptions or not. That's why the standards committee for C++11 ditched throw(...) in favor of noexcept, which states that the function will throw nothing.

As for compile-time error checking, Java clearly shows how well this works. You're writing a function, foo. Your design has that it throws X and Y. Other pieces of code use foo, and they throw whatever foo throws. But the exception specification doesn't say "whatever foo throws". It must list X and Y specifically.

Now you go back and change foo so that it no longer throws X, but now it throws Z. Suddenly, the entire project stops compiling. You must now go to every function that throws whatever foo threw just to change its exception specification to match foo.

Eventually, a programmer just throws up their hand and says that it throws any exception. When you abandon a feature like that, it's a de facto admission that the feature is doing more harm than good.

It's not that they cannot be useful. It's just that actual use of them shows that they're generally not useful. So there's no point.

Plus, remember that C++'s specification states that no specification means that anything will be thrown, not nothing (as in Java). The simplest way to use the language is exactly that way: no checking. So there are going to be plenty of people who don't want the bother of using it.

What good is a feature that many don't want to bother with, and even those who do will generally get a lot of grief out of it?

How will C++17 exception specifier type system work?

According to this (accepted) proposal, since C++17; as noexcept is part of function type, will the code above print true?

Yes.

The type of f will be deduced to void(*)() noexcept since the function-to-pointer conversion applied to asdf will preserve the noexcept property. A call to a noexcept function pointer certainly cannot throw an exception, unless one of its subexpressions does.

For the exact wording, see [expr.unary.noexcept]/3 and [expect.spec]/13. Note that the new wording in the latter paragraph from the C++17 draft comes from P0012R1, which is linked in the OP.

The result of the noexcept operator is true if the set of potential exceptions of the expression ([except.spec]) is empty, and false otherwise.

...

  • If e is a function call ([expr.call]):

    • If its postfix-expression is a (possibly parenthesized) id-expression ([expr.prim.id]), class member access ([expr.ref]), or pointer-to-member operation ([expr.mptr.oper]) whose cast-expression is an id-expression, S is the set of types in the exception specification of the entity selected by the contained id-expression (after overload resolution, if applicable).
      ...

So the set of potential exceptions of f() is the same as the set of types in the exception specification of f, which is empty since f is declared noexcept.

Let's move on to the second question:

The noexcept specifying seems ignored in C++14 or 11. Will this code work as intended in C++17?

Your question seems to be: will std::function<void() noexcept> refuse to hold a function that can throw exceptions?

I would say that it's unclear. In the present wording of the standard, std::function<void() noexcept> is not actually defined, just as std::function<double(float) const> is not defined. This was of course not an issue in C++14, since noexcept was not considered part of a function's type.

Will std::function<void() noexcept> simply break in C++17? That's uncertain to me. Let's take a look at the current wording to guess what the behaviour "should" be.

The standard requires the argument to the constructor of std::function<R(ArgTypes..)> to be "Lvalue-Callable" for argument types ArgTypes... and return type R, which means:

A callable type ([func.def]) F is Lvalue-Callable for argument types ArgTypes and return type R if the expression INVOKE(declval<F&>(), declval<ArgTypes>()..., R), considered as an unevaluated operand (Clause [expr]), is well formed ([func.require]).

Perhaps there should be an additional requirement that if the function type is noexcept, then noexcept(INVOKE(...)) must be true as well. Nonetheless, this wording is not present in the current draft.

In P0012R1, there is a comment that:

It is an open issue how to propagate "noexcept" through std::function.

My guess is that they mean that it is not clear how std::function could be implemented if this additional requirement were imposed. Hopefully, someone else can provide more details.

Exception specifications when deriving from std::exception in C++11

Empty throw specifications are useful, as they actually enable compiler optimizations at the caller's site, as Wikipedia knows (I don't have a technical quote handy).

And for reasons of optimization opportunities, nothrow-specifications are not deprecated in the upcoming standard, they just don't look like throw () any more but are called noexcept. Well, yes, and they work slightly differently.

Here's a discussion of noexcept that also details why traditional nothrow-specifications prohobit optimizations at the callee's site.

Generally, you pay for every throw specification you have, at least with a fully compliant compiler, which GCC has, in this respect, appearantly not always been. Those throw specifications have to be checked at run-time, even empty ones. That is because if an exception is raised that does not conform to the throw specification, stack unwinding has to take place within that stack frame (so you need code for that in addition to the conformance check) and then std::unexpected has to be called. On the other hand, you potentially save time/space for every empty throw specification as the compiler may make more assumptions when calling that function. I chicken out by saying only a profiler can give you the definitive answer of whether your particular code suffers from or improves by (empty!) throws specification.

As a workaround to your actual problem, may the following work?

  • Introduce #define NOTHROW throw () and use it for your exception's what and other stuff.
  • When GCC implements noexcept, redefine NOTHROW.

Update

As @James McNellis notes, throw () will be forward-compatible. In that case, I suggest just using throw () where you have to and, apart from that, if in doubt, profile.

Can anyone explain C++ exception specifications to me?

When are they used (I have rarely seen it used in code)

Hopefully never as they are deprecated in the next version of C++ due for standardization next year.

What are the pros and cons (benefits/disadvantages) of using exception specifications?

They provide a way for readers of your code to know exactly what exceptions a function is allowed to throw. The problem is, that if an unexpected exception (one not in the specification) is thrown, then the program will be terminated (by default).

Do I specify exception types in just the function header or declarations as well? (C++)

15.4/2:

If any declaration of a function has an exception-specification, all declarations, including the definition and an explicit specialization, of that function shall have an exception-specification with the same set of type-ids.

Questions on Exception Specification and Application Design

1/ Do Herb Sutter's rants about exception specification still hold today? Has anything changed since then? I am mostly interested in pre-C++0x standards. If yes, I guess we can consider this topic closed.

Yes, they still hold.

Exceptions specifications are:

  • half-way implemented (function pointers don't specify exceptions, for example)
  • not checked at compile-time, but leading to termination at runtime !!

In general, I would be against exceptions specifications, because it causes a leak of implementation details. Look at the state of Java exceptions...

In C++ in particular ? Exceptions specifications are like shooting yourself in the foot since the tiniest error in documentation may lead to a std::terminate call. Note that almost all functions may throw a std::bad_alloc or a std::out_of_range for example.

Note: Since C++11, throw() was deprecated and now with C++17 it is gone; instead, from C++17 on, the noexcept(false) specifier can be used. It is better supported in function pointers, but still leads to termination at run-time rather than errors at compile-time.


2/ Since it seems that the compiler mostly ignores these exception specifications, and when catching exceptions, in 99% of the cases, one would use catch (const CustomException &ex), how would one specify that a function throws CustomException? throw(CustomExecption) or throw (CustomException &) or throw (const CustomException &)? I have seen all variations, and, although I would go for the first one, do the others make any sense / add any benefits?

The compiler does not ignore the exceptions specifications, it sets up very vigilant watchdogs (which axes) to make sure to kill your program in case you had missed something.


3/ How would one actually use this functionality, and, at the same time, avoid the fallacies illustrated in the above 3rd fact?

Your customer will appreciate if it stays informal, so the best example is:

void func(); // throw CustomException

and this lets you focus on the exceptions that matter too, and let "unimportant" exceptions slip through. If a consumer wants them all ? catch(std::exception const& e) works.


4/ EDIT: Suppose that we're building a library. How will its users know what exceptions to expect, if we don't use exception specification? They will certainly not see what functions will be called internally by the API methods...

Do they have to ?

Document what matters, std::exception or ... take care of the unexpected.



Related Topics



Leave a reply



Submit