Reputation: 927
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
Reputation: 71899
Yes, the list of things that can throw in C++ can be exhaustively defined.
throw
expressionnew
can throw bad_alloc
dynamic_cast
can throw bad_cast
typeid
can throw bad_typeid
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
Reputation: 40604
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
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