Reputation: 50054
I am thinking about writing non-copyable exception classes. I find it interesting, because then I don't have to worry about exceptions that could be thrown during allocations within the copy-constructor. If the creation of the exception object succeeds, everything is fine and there should be no issues with std::terminate
.
struct exception
{
exception() = default;
exception(const exception&) = delete;
exception(exception&&) noexcept = default;
~exception() noexcept = default;
auto operator=(const exception&) -> exception& = delete;
auto operator=(exception&&) noexcept -> exception& = delete;
};
int main()
{
try {
try {
throw exception{};
} catch (...) {
std::rethrow_exception(
std::current_exception());
}
} catch (const exception& e) {
return 1;
}
}
GCC-4.7 and Clang-3.2 accept the above code. However, I am a bit surprised. As far as I know, there are several situations where exception objects might be copied, e.g. std::current_exception()
and std::rethrow_exception()
.
Question: Is the above code correct according to C++11, i.e. will it be accepted by all compilers conforming to C++11?
Edited: Added std::rethrow_exception
and std::current_exception
to the example. Both compilers accept this version. This should make it clear, that if the compiler doesn't require a copy-constructor when an exception is thrown, the compiler won't require one when these two functions are used.
Upvotes: 3
Views: 1108
Reputation: 473567
However, I am a bit surprised. As far as I know, there are several situations where exception objects might be copied, e.g.
std::current_exception()
andstd::rethrow_exception()
.
But you don't call any of them. The standard is quite clear about how the exception object is initialized. From 15.1, p3:
A throw-expression initializes a temporary object, called the exception object , the type of which is determined by removing any top-level cv-qualifier s 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.
In short, it acts like returning a type by value: the return value/exception object is initialized by the expression you provide. Because the expression you use is a temporary, it will act like returning a temporary from a function and invoke the move constructor. Granted, odds are good that this will be elided, but that's the point that 15.1, p5 makes:
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).
This is just as true for returning values: the return value is initialized by copy/move initialization, where appropriate. Thus, the appropriate constructors have to be accessible, even if they're elided.
You can't throw your exception class in a way that would require copy-construction of the exception object. So you can't throw an lvalue; you can only throw a prvalue or an xvalue.
Nowhere in the standard does it say that the system is allowed to arbitrarily copy the exception for no reason. A call to std::current_exception
may copy it. A call to std::rethrow_exception
will likely copy it.
But if you don't call things that explicitly copy your exception objects, C++ is not allowed to do so willy-nilly.
Upvotes: 2
Reputation: 279285
current_exception
says it refers either to the current exception or to a copy of it it, but doesn't say which. This suggests to me that:
current_exception
on it)Just throwing the thing and catching it by reference is fine. The temporary in the throw
expression is "used to initialize" the object used by the implementation to keep the current exception, so it can be moved or copied (according to which the class supports), and that move/copy can be elided.
For what it's worth, make_exception_ptr
is specified to always copy. You could perhaps argue that this is a defect: e
can be a by-value parameter in which case moving might be better. But that's my impetuous and ignorant impression, I've never even seen these functions before now.
[*] It's explicitly unspecified whether or not current_exception
"creates a new copy each time it's called", but I'm not wholly certain whether that is intended to imply that it's unspecified whether it creates a new copy the first time it's called.
Upvotes: 2