问题
In More Effective C++, Scott Meyers says
C++ specifies that an object thrown as an exception is copied.
I suppose then, that if the copy constructor throws an exception in turn, std::terminate
is called, so this is a good reason for declaring all my exceptions' copy constructors noexcept
(and also, I guess, to not throw objects which allocate memory from the heap, like std::string
).
Yet I was surprised to see that the standard library implementation shipped with GCC 4.7.1 doesn’t define those copy constructor for std::bad_alloc
and std::exception
. Shouldn’t they define them noexcept
?
回答1:
Section 18.8.1 [exception]/p1 specifies:
namespace std {
class exception {
public:
exception() noexcept;
exception(const exception&) noexcept;
exception& operator=(const exception&) noexcept;
virtual ~exception();
virtual const char* what() const noexcept;
};
}
I.e. the copy constructor and copy assignment of std::exception shall be noexcept
, and this is testable with:
static_assert(std::is_nothrow_copy_constructible<std::exception>::value, "");
static_assert(std::is_nothrow_copy_assignable<std::exception>::value, "");
I.e. if an implementation does not make these members noexcept, then it is not conforming in this regard.
Similarly, 18.6.2.1 [bad.alloc]/p1 also specifies noexcept copy:
namespace std {
class bad_alloc : public exception {
public:
bad_alloc() noexcept;
bad_alloc(const bad_alloc&) noexcept;
bad_alloc& operator=(const bad_alloc&) noexcept;
virtual const char* what() const noexcept;
};
}
Additionally, all of the std-defined exception types have noexcept copy members, either explicitly, or implicitly. For the types defined in <stdexcept>
this is usually implemented with a reference-counted buffer for the what()
string. This is made clear in [exception]/p2:
Each standard library class
T
that derives from class exception shall have a publicly accessible copy constructor and a publicly accessible copy assignment operator that do not exit with an exception. ...
That is, in a quality implementation (and it does not take heroics to create a quality implementation in this regard), not only will the copy members of exception types not throw an exception (naturally because they are marked noexcept
), they also will not call terminate()
.
There is no failure mode for copying the std-defined exception types. Either there is no data to copy, or the data is reference counted and immutable.
回答2:
Memory allocation for exceptions is done outside the regular channels:
15.1 Throwing an exception [except.throw]
3 Throwing an exception copy-initializes (8.5, 12.8) a temporary object, called the exception object. [...]
4 The memory for the exception object is allocated in an unspecified way, except as noted in 3.7.4.1. [...]
3.7.4.1 Allocation functions [basic.stc.dynamic.allocation]
4 A global allocation function is only called as the result of a new expression (5.3.4), or called directly using the function call syntax (5.2.2), or called indirectly through calls to the functions in the C++ standard library. [ Note: In particular, a global allocation function is not called to allocate storage for [...] for an exception object (15.1). —end note ]
Most implementations have a separate memory region from which exception objects are allocated so that even if you are re-throwing a std::bad_alloc
exception object, the exhausted free store itself is not being asked to allocate the copied exception object. So there should not be a reason for the copying itself to generate another exception.
回答3:
Well, it's all fine and good to declare it noexcept
, but it requires that you can GUARANTEE that it won't throw an exception (and for portable code, in all it's implementations!). Which I expect the is the reason that the standard ones aren't declared that way.
There is clearly no harm in declaring the copy constructor noexcept
, but it can be pretty restrictive to try to achieve that.
来源:https://stackoverflow.com/questions/21644735/should-i-declare-the-copy-constructor-of-my-exceptions-noexcept