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 the constructor fails/throws, w doesn't exist. So "w.throwException()" is not valid.
Related
While reading about the details of exception handling in the standard, I noticed that it doesn't ever seem to actually specify that the operand to a throw expression will provide the value for the thrown exception object.
The [expr.throw] section describing the semantics of a throw expression says only that:
Evaluating a throw-expression with an operand throws an exception (15.1); 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.
So if someone says throw 5, the type of the exception object must be int. This does not seem to actually say that the value of the exception object must be 5 rather than, say, 7 or any other arbitrary value of type int. Section 15.1 [except.throw] continues to be just as nebulous. It says things like:
An object is passed and the type of that object determines which handlers can catch it.
Once again, the the type of the operand matters, but not the value of the operand.
Throwing an exception copy-initializes a temporary object, called the exception object.
A temporary object is copy-initialized, but no mention is made of this temporary object having any particular value. The compiler could copy-initialize the temporary exception object from a prvalue int with value 7 if it wanted to, and it would still satisfy this clause because it did copy-initialize a temporary object. The operand to the throw expression is never mentioned.
So, if I have this program:
#include <cassert>
void f() {
try {
throw 5;
} catch (int x) {
assert(x == 5);
}
}
What specific wording in the C++ standard guarantees that this assertion will never fail? As far as I can tell, the standard only guarantees that the exception object will have type int but not that it will have any particular value, i.e. the assertion could fail if the temporary object were initialized with a value of 7. There doesn't seem to be any mention of the object/value/result of the operand to the throw expression being used to create the exception object.
Consider the fragment:
try {
Foo f;
throw std::move(f);
}
catch (Foo& f) { }
[expr.throw] says that:
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.
which would be Foo&&. The exception object is then initialized according to [except.throw]:
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 declared 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.
This suggests to me that the exception object is initialized as:
Foo&& __exception_object = std::move(f);
and that the handler would not match. However, both gcc and clang do catch this exception. So what's the actual type of the exception object here? If Foo, why?
The static type of an expression is never a reference type.
1.3.24 [defns.static.type] defines "static type":
type of an expression (3.9) resulting from analysis of the program without considering execution semantics
the first step in that "analysis of the program" is to remove references, see
5 [expr] p5 and Expressions can have reference type
If an expression initially has the type “reference to T” (8.3.2, 8.5.3), the type is adjusted to T prior to any further analysis. The expression designates the object or function denoted by the reference, and the expression is an lvalue or an xvalue, depending on the expression.
So std::move(f) is an xvalue expression, with static type Foo.
You don't need to involve rvalues to demonstrate this, the same was true in C++03 with:
int& f();
throw f();
This throws an int not an int&.
Without considering the specifics, an exception object is an object, and a reference is not an object, so an exception object cannot be a reference. It must be an object.
I have always read that temporaries are allowed to bind only with non-const reference arguments in case of function calls..
CASE 1:-
For example:-
class Simple{
public:
int i;
Simple(Simple &f)
{
i = f.i + 1;
}
Simple(int j)
{
i = j;
}
};
int main()
{
Simple f1 = Simple(2); // error no matching call fruit::fruit(fruit)...
return 0;
}
This would give me error as I am trying to bind temporary with non-const reference arguments.
CASE 2:-
try
{
throw e;
}
catch ( exception& e )
{
}
I have learnt when we throw an exception what really get passed to catch is the copy of original exception thrown i.e a temporary is created for the object thrown and then this would be passed to catch clause.
What catch is doing is catching this exception by non-const reference. This is in contrast to what I have shown in CASE 1.
So, my questions are:-
1) Are there specific scenarios where binding temporary to non-const reference is allowed.
2) If there are then what factors are taken into account while allowing these exceptions.
Are there specific scenarios where binding temporary to non-const reference is allowed.
For lvalue-references (that is, the type T&) there isn't. You can't bind a temporary to a non-const lvalue-reference because it doesn't make much sense to modify, say, a literal like 42. They can bind to const lvalue-references because then a promise has been made not to modify the object to which the reference is bound.
They can in fact bind to rvalue-references (T&&), but that's unrelated to this thread.
If there are then what factors are taken into account while allowing these exceptions.
It is true that temporaries cannot bind to non-const lvalue-references, but there's a certain provision made for exception objects:
Taken from the C++11 standard (closest draft n3337):
§15.1/3 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). [..]
emphasis mine
cppreference simplifies this into:
Unlike other temporary objects, the exception object is considered to be an lvalue argument when initializing the catch clause parameters, so it can be caught by lvalue reference, modified, and rethrown.
So for this case you can in fact bind the exception to a non-const lvalue-reference.
One can throw exception of type int,float,long or custom data types like classes and structs. But which data type can't be thrown as exception in C++?
Exception can not throw incomplete type:
§ 15.1
If 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.
§ 15.5
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).
15.3.1 Handling an exception
The exception-declaration in a handler describes the type(s) of exceptions that can cause that handler to be entered. The exception-declaration shall not denote an incomplete type or an rvalue reference type. The exception-declaration shall not denote a pointer or reference to an incomplete type, other than void*, const void*, volatile void*, or const volatile void*.
Imagine two similar pieces of code:
try {
[...]
} catch (myErr &err) {
err.append("More info added to error...");
throw err;
}
and
try {
[...]
} catch (myErr &err) {
err.append("More info added to error...");
throw;
}
Are these effectively the same or do they differ in some subtle way? For example, does the first one cause a copy constructor to be run whereas perhaps the second reuses the same object to rethrow it?
Depending on how you have arranged your exception hierarchy, re-throwing an exception by naming the exception variable in the throw statement may slice the original exception object.
A no-argument throw expression will throw the current exception object preserving its dynamic type, whereas a throw expression with an argument will throw a new exception based on the static type of the argument to throw.
E.g.
int main()
{
try
{
try
{
throw Derived();
}
catch (Base& b)
{
std::cout << "Caught a reference to base\n";
b.print(std::cout);
throw b;
}
}
catch (Base& b)
{
std::cout << "Caught a reference to base\n";
b.print(std::cout);
}
return 0;
}
As written above, the program will output:
Caught a reference to base
Derived
Caught a reference to base
Base
If the throw b is replace with a throw, then the outer catch will also catch the originally thrown Derived exception. This still holds if the inner class catches the Base exception by value instead of by reference - although naturally this would mean that the original exception object cannot be modified, so any changes to b would not be reflected in the Derived exception caught by the outer block.
In the second case according to C++ Standard 15.1/6 copy constructor is not used:
A throw-expression with no operand rethrows the exception being handled. The exception is reactivated with the existing temporary; no new temporary exception object is created. The exception is no longer considered to be caught; therefore, the value of uncaught_exception() will again be true.
In the first case new exception will be thrown according to 15.1/3:
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 used to initialize the variable named in the matching handler (15.3). The type of the throw-expression shall not be an
incomplete type, or a pointer or reference to an incomplete type, other than void*, const void*,
volatile void*, or const volatile void*. 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.
In both cases copy constructor is required at throw stage (15.1/5):
When the thrown object is a class object, and the copy constructor used to initialize the temporary copy is not accessible, the program is ill-formed (even when the temporary object could otherwise be eliminated).
Similarly, if the destructor for that object is not accessible, the program is ill-formed (even when the temporary object could otherwise be eliminated).