Pergunta

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?

Foi útil?

Solução

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.

Outras dicas

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.

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.

They must be noexcept since C+11 and throw() before C++11 (https://en.cppreference.com/w/cpp/error/exception/exception, https://en.cppreference.com/w/cpp/memory/new/bad_alloc), as well as other standard library exceptions. If they are not except, but throw(), this just means that the compiler version does not support C++11 in that part, but does according to earlier versions of the language. If they are neither noexcept nor throw(), that means the version of the compiler does not implement correctly neither C++11 nor earlier versions of the language.

As for

C++ specifies that an object thrown as an exception is copied.

, Since C++11 (before C++11, standards didn't regulate the matter) in some circumstances compilers are permitted (though, not obligated) to omit copying when throwing and catching exceptions and in some circumstances compilers (since C++11) must to move to an exception object when throwing instead of copying. So, though there are cases when compilers must copy an object when throwing, in other cases it may happen that there is no copying when throwing, as well as, though there are cases when compilers must copy an exception object when catching, it may happen there is no copying then catching. Detailed here: https://en.cppreference.com/w/cpp/language/copy_elision.

Also, you should avoid exceptions to be thrown out of constructors and move constructors of your types (classes) used for exceptions since they could be called when throwing the exception (and there are a lot of things here that a compiler chooses - see the same link https://en.cppreference.com/w/cpp/language/copy_elision) and throwing the additional exception would prevent throwing the initial one, which would be just lost. As well as an exception from a copy constructor when throwing would cause the same.

As for should you declare your copy constructor noexcept,

Yes, you should declare it as “noexcept” - it is a matter of performance. But the main matter here is that you must guarantee that it does not throw exceptions out of itself in fact.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top