Should I declare the copy constructor of my exceptions noexcept?
Asked Answered
P

4

13

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?

Pilloff answered 8/2, 2014 at 10:16 Comment(1)
Just FYI, GCC 4.7.1 was released 9 years ago.Gambrill
B
5

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.

Braille answered 8/2, 2014 at 18:8 Comment(4)
So am I right in reading into this that the phrase "does not exit with an exception" allows a poor implementation to call std::terminate (i.e in the body to throw an exception and as a result of the noexcept to call terminate)?Dietrich
@JohannesSchaub-litb: I recently read an answer to another SO question which proposed that the constructor of std::chrono::seconds might call this_thread::sleep. So, in the spirit of "an implementation so ridiculous that its time on the market would be measured in milliseconds", yeah, sure. The standard isn't a perfect document, and could never guard against such absurdity 100%. My hope is that it won't even try. There are so many far more important things it needs to do.Braille
This doesn't answer the question, though, does it? OP put "my exceptions" in the title, which means user-defined exception classes. You're only talking about std-defined exception classes. Maybe you just need to add some exhortation to follow the Standard's example.Rightness
noexcept does not prevent an attempt to throw out an exception, which will cause calling std::terminate.Vraisemblance
R
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.

Rochette answered 8/2, 2014 at 12:10 Comment(1)
Allocating the exception object in the specific memory area with guarantee of no exception while allocating the memory for the exception object has nothing with whether or not the object copy constructor uses regular memory allocations. Consider the exception object copies its member of std::string that uses the default memory allocator type – you still have the possibility of std::bad_alloc. Moreover, there exist other types exceptions besides std::bad_alloc.Vraisemblance
L
0

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.

Lovieloving answered 8/2, 2014 at 10:23 Comment(2)
shouldn’t the standard guarantee that its exceptions' copy constructor will not throw?Pilloff
That may not be possible tho'.Lovieloving
V
0

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.

Vraisemblance answered 24/8, 2021 at 17:13 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.