I have the following C++ code
template <class E>
class ExceptionWrapper {
public:
explicit ExceptionWrapper(const E& e): e(e) {}
void throwException() {
throw e;
}
private:
E e;
};
...
try {
ExceptionWrapper<E> w(...);
w.throwException();
} catch (const E& e) {
...
}
...
Question: is this code valid? I could argue that returning a reference to the class member is almost always not valid (and I am sure everybody agrees with this statement). However, my colleague claims that this is not the case with throw
.
P.S. after changing catch (const E& e)
to catch (E e)
a nasty bug seemingly disappeared which strengthens my position - that this code is not valid.
my claim is that catching e by reference is not valid since e is a member of w and w is not alive in the catch scope.
Your claim is incorrect. throw e;
throws a copy of the member, and that copy is valid in catch's scope.
§ 15.1 / 3 (n3797 draft):
Throwing an exception copy-initializes ( 8.5 , 12.8 ) a temporary object, called the exception object . The temporary is an lvalue and is used to initialize the variable named in the matching handler ( 15.3 ). If the type of the exception object would be an incomplete type or a pointer to an incomplete type other than (possibly cv-qualified) void the program is ill-formed. Evaluating a throw-expression with an operand throws an exception; the type of the exception object is determined by removing any top-level cv-qualifiers from the static type of the operand and adjusting the type from “array of T ” or “function returning T ” to “pointer to T ” or “pointer to function returning T ,” respectively.
Catching by const reference is the preferred way to catch exceptions. It allows catching derivatives of std::exception
without slicing the exception object.
I think the relevant point is :
15.1. Throwing an exception:
p3. A throw-expression initializes a temporary object, called the exception object, the type of which is determined by removing any top-level cv-qualifiers from the static type of the operand of throw and adjusting the type from “array of T” or “function returning T” to “pointer to T” or “pointer to function returning T”, respectively. The temporary is an lvalue and is used to initialize the variable named in the matching handler (15.3). If the type of the exception object would be an incomplete type or a pointer to an incomplete type other than (possibly cv-qualified) void the program is ill-formed. Except for these restrictions and the restrictions on type matching mentioned in 15.3, the operand of throw is treated exactly as a function argument in a call (5.2.2) or the operand of a return statement.
This is from the draft for c++11, emphasis mine.
It basically means there is a temporary object created from the argument of throw
. Just like there was a function E f(){return private_e;}
, and that temporary is used as the argument for the appropriate handler. So you would have two possible copies actually if you didn't catch by reference.
Probably also relevant:
p5. When the thrown object is a class object, the copy/move constructor and the destructor shall be accessible, even if the copy/move operation is elided (12.8).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With