17

std::exception requires that its constructor be throw(). Yet std::runtime_error accepts a std::string as its argument, which indicates that it's storing a std::string somewhere. Therefore, an assignment or copy construction has to be going on somewhere. And for std::string, that's not a nothrow operation.

How then does runtime_error::runtime_error meet throw()?

(For context, I'm implementing an exception type, and want to store a few std::strings from the call site, and I want to do it correctly...)

Roddy
  • 66,617
  • 42
  • 165
  • 277
Billy ONeal
  • 104,103
  • 58
  • 317
  • 552
  • Without diving into library code, a simple solution would be to catch the thrown exception on the allocation failure, and fall back on some safe alternative, like a small buffer built into the type and truncating the result. – Dennis Zickefoose Jul 28 '11 at 18:47
  • @Dennis: That won't work either, because the `string` gets default-constructed before you enter the constructor of `runtime_error`. – Billy ONeal Jul 28 '11 at 18:49
  • You assume it is storing a string directly, and not just an array of char objects. Also, being a library function, they know if a string can throw when default constructed. – Dennis Zickefoose Jul 28 '11 at 18:50
  • @billy, it could also just have a properly aligned `char` array of `sizeof(std::string)`, then placement-new the string into that array, catching exceptions. This allows you to detect if the `string` constructor will throw without needing to resort to library implementation details – bdonlan Jul 28 '11 at 19:36

2 Answers2

9

(Here's the same thing in a minimal-ish testcase.)


runtime_error::runtime_error(string const&) doesn't need to meet throw().

It doesn't inherit from or override exception::exception(), and by the time string's copy constructor is invoked, exception::exception() has completed.

If copying the string were to throw an exception, this would unwind runtime_error::runtime_error(string const&) and then, I suppose, invoke exception::~exception().


It's hard to directly show that there is no requirement of a derived ctor to meet a base ctor's exception specifier, but it is strongly implied by the following passage (which describes how the base's destructor is invoked, rather than passing the exception into the base constructor):

[2003: 15.2/2] An object that is partially constructed or partially destroyed will have destructors executed for all of its fully constructed subobjects, that is, for subobjects for which the constructor has completed execution and the destructor has not yet begun execution. Should a constructor for an element of an automatic array throw an exception, only the constructed elements of that array will be destroyed. If the object or array was allocated in a new-expression, the matching deallocation function (3.7.3.2, 5.3.4, 12.5), if any, is called to free the storage occupied by the object.

The only passage which comes even close to the scenario you presumed (and which I initially presumed) is the following.

[2003: 15.4/3] If a virtual function has an exception-specification, all declarations, including the definition, of any function that overrides that virtual function in any derived class shall only allow exceptions that are allowed by the exception-specification of the base class virtual function.

But clearly exception::exception() is not a virtual function, and clearly runtime_error::runtime_error(string const&) does not override it.

(Note that this scenario would apply for a virtual destructor; accordingly, you can see that, in libstdc++, runtime_error::~runtime_error() is throw()).

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
  • Oh, so `std::exception` must be nothrow, but classes derived from `std::exception` may throw in their constructors? – Billy ONeal Jul 28 '11 at 18:54
  • 1
    The default constructor (and copy constructor) must be `nothrow`; the constructor accepting a string must not be. It seems you're using the signature of the default constructor to refer to all constructors? – Potatoswatter Jul 28 '11 at 19:20
  • @Potatoswatter: `exception` has no constructor accepting a string. `runtime_error` has no default constructor ([and that would not be required to be `nothrow` either](http://codepad.org/JvO2J1gB)). – Lightness Races in Orbit Jul 28 '11 at 19:22
  • 1
    About the update, see §18.8.1/2: "Each standard library class T that derives from class exception shall have a publicly accessible copy con- structor and a publicly accessible copy assignment operator that do not exit with an exception." – Potatoswatter Jul 28 '11 at 19:23
  • @Potatoswatter: OK, but this question is not about the copy constructor or copy assignment operator. – Lightness Races in Orbit Jul 28 '11 at 19:25
  • @Tomalak: Sorry, saying "default constructor" was a brain fart, but the copy constructor is the hard part, §18.8.1/2 still stands. – Potatoswatter Jul 28 '11 at 19:26
  • @TomalakGeret'kal let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/1933/discussion-between-potatoswatter-and-tomalak-geretkal) – Potatoswatter Jul 28 '11 at 19:26
  • @Potatoswatter: §18.8.1/2 is still irrelevant. – Lightness Races in Orbit Jul 28 '11 at 19:27
  • 4
    I really dislike that they suggest comment discussions be brought to chat. Comments are the perfect place to elaborate and work out kinks, *and keep a record of it*. Gr. – GManNickG Jul 28 '11 at 20:50
  • 4
    @GMan: Can't disagree with you there. I also hate seeing "let us continue this discussion in chat" when I really don't want to. It's not enforced by the technology, but "chat" _feels_ real-time, and I don't come on Stack Overflow to interact in real-time. So the feature _rushes me_. – Lightness Races in Orbit Jul 29 '11 at 00:02
7

Update, 2015:

Yet std::runtime_error accepts a std::string as its argument, which indicates that it's storing a std::string somewhere. Therefore, an assignment or copy construction has to be going on somewhere. And for std::string, that's not a noexcept operation.

runtime_error (and logic_error) are only required to accept an argument of type std::string const &. They are not required to copy it.

Use these overloads at your own peril. LLVM libc++ does not provide storage.

On the other hand, GNU libstdc++ tiptoes carefully to avoid running out of memory. It copies the contents of the string, but into the exception storage space, not into a new std::string.

Even then, it adds an std::string&& overload and uses friendship to adopt the internal buffer of a std::string argument passed by rvalue, to conserve exception storage space too.

So that's your real answer: "Very carefully, if at all."

You could leverage GCC's generosity by using std::runtime_errors as members of your own exception class, storing one string each. This would still be useless on Clang, though.


Original answer, 2011. This is still true:

An exception during stack unwinding causes terminate to be called.

But constructing the object to be thrown is not part of unwinding, and is treated no differently from the code before the throw expression.

If std::runtime_error::runtime_error( std::string const & ) throws std::bad_alloc, the runtime_error exception is lost (it never existed) and the bad_alloc is handled instead.

Demonstration: http://ideone.com/QYPj3

As for your own class storing std::strings from the call site, you'll want to follow §18.8.1/2:

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.

This is required because copying from the stack to the thread's exception storage is sensitive to exceptions. §15.1/7:

If the exception handling mechanism, after completing evaluation of the expression to be thrown but before the exception is caught, calls a function that exits via an exception, std::terminate is called (15.5.1).

So, you should use a shared_ptr< std::string > or some such to sanitize copies after the first.

Community
  • 1
  • 1
Potatoswatter
  • 134,909
  • 25
  • 265
  • 421