Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ throwing class members

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.

like image 706
Prof. Legolasov Avatar asked Oct 20 '15 16:10

Prof. Legolasov


2 Answers

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.

like image 181
eerorika Avatar answered Sep 20 '22 18:09

eerorika


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).

like image 24
luk32 Avatar answered Sep 22 '22 18:09

luk32