Dmitry J
Dmitry J

Reputation: 927

What can (and what can't) throw an exception in c++?

Is there complete list (maybe recursively defined) of "code statements" that can lead to exception in c++? Something like this:

1) throw statement (naturally)

2) calls to new

3) calls to any functions from standard library that are documented to be able to throw.

4) calls to any user-defined functions (including constructors) that contain operations from 1-3.

5) Something else? Allocating local objects on stack, operations on built-in types, dereferencing pointers, type casts - are they able to throw?

6) Everything else is exception-free.

By exception-free I don't mean operations that are always successful. Dereferencing a pointer surely isn't. But still it does not make sense to wrap it in try-catch block, think about exception-safety of function dereferencing a pointer, etc. So the code that either successful or leads to undefined behaviour can be considered as exception-free.

Upd. Despite my last paragraph I still got a comment that undefined behaviour also can throw, so let me explain what I mean. Consider following code:

void bar();
Class C{
...
public:
  foo() {
    something_that_breaks_class_invariants;
    bar();
    something_that_restores_class_invariants;
  }
}

If I correctly understand what exception safety is about, then if bar() can throw exception, this code is bad. I should change the order of statements, or I should wrap bar() in try-catch block, restore class invariants and propagate exception further.

But if bar() either successfully returns or leads to undefined behaviour (because, I don't know, something else is broken), then foo() is ok. foo() can't do anything and shouldn't care about possible undefined behaviour of bar(). In this sense bar() is exception-free, can be marked noexcept, etc.

So my question is: what kinds of statements can be in bar() to consider it exception-free is this sense?

Upvotes: 5

Views: 1470

Answers (3)

Sebastian Redl
Sebastian Redl

Reputation: 71899

Yes, the list of things that can throw in C++ can be exhaustively defined.

  • throw expression
  • new can throw bad_alloc
  • dynamic_cast can throw bad_cast
  • typeid can throw bad_typeid
  • any call to a function that is not noexcept or throw()

The last point also applies to all the implicit function calls of C++: default/copy/move constructors, overloaded operators, destructors (note that those default to noexcept) and conversion operators.

If you are ever in doubt about a particular expression, you can use the noexcept operator to have the compiler tell you whether it's theoretically possible for the expression to throw.

Upvotes: 2

The answer is simple: Any statement that uses an overloadable operator or function that cannot be proven not to throw, can throw. Here are some examples:

template <class T> T foo(const T& arg) { return arg; }    //can throw (copy constructor!)
template <class T> void foo(T a, T b) { a+b; }    //can throw (+ is overloadable)

template <class T>
void foo(T iter, T end) {
    for(; iter < end; iter++) {    //both the < and the ++ operator can throw
        iter->bar();    //even the -> operator is overloadable and can throw
    }
}

In short, whenever you do not have knowledge of the involved types, as is generally the case with templates, you pretty much have to call any statement a throwing statement.

Afaik, the only valid defense against this not to allow exceptions in the first place.

Upvotes: 0

MSalters
MSalters

Reputation: 179779

Your list is almost complete. There's indeed one cast that can throw: dynamic_cast<Derived&>. That's because there are no null references. dynamic_cast<Derived*> returns a null pointer instead of throwing.

In all cases, exceptions are thrown when evaluating expressions. Variable definitions may only throw when they contain an expression, e.g. in an initializer or inside a constructor.

The "Undefined Behavior" bit is a red herring. You simply can't reason about a C++ program that has Undefined Behavior, not even about what might have happened prior to that UB. That means we're assuming no UB whenever we are reasoning about defined C++ behavior.

Upvotes: 0

Related Questions