Exceptions defined in <stdexcept>
(e.g. std::logic_error
, std::runtime_error
and their subclasses such as std::system_error
) have constructors expecting string arguments, e.g.:
domain_error(const string& what_arg);
domain_error(const char* what_arg);
with postconditions
strcmp(what(), what_arg.c_str()) == 0
strcmp(what(), what_arg) == 0
respectively. There is no requirement that these arguments passed to the constructors remain valid during the lifetime of these exceptions, so the only way to ensure that the postconditions hold, is to duplicate and store these dynamic strings. This requires memory, so I assume that their construction itself may throw std::bad_alloc
or similar, which is usually most unexpected. This causes problems, because every code example I've seen in the wild encourages people to write code like
if (haveError)
throw std::runtime_error("BOO!"); // May throw std::bad_alloc instead?!
whereas it would seem to be much safer to construct the exception beforehand in some other place, e.g:
struct A {
// During allocation of A one would often expect std::bad_alloc anyway:
A() : m_someException("BOO!") {}
void f() {
/* Do stuff */
if (haveError)
throw m_someException;
/* Note that according to §18.8.1.2 all standard library
classes deriving from `std::exception` must have publicly
accessible copy constructors and copy assignment operators
that do not exit with an exception. In implementations such
exception instances most likely share the common string
with all their copies. */
}
std::runtime_error const m_someException;
};
This makes me very cautious of libraries which throw any such exceptions, e.g even regex_error
from <regex>
in C++11!!!
Why don't these exceptions have no-throw/noexcept constructors? Does the C++ core guidelines have a say on this?
PS: Personally I would have left what()
a pure abstract method at this point in the exception ancestry chain.
EDIT 09.10.2017: Here's a PoC demonstrating that std::runtime_error
construction can throw a std::bad_alloc
instead:
#include <cstddef>
#include <cstdlib>
#include <new>
#include <stdexcept>
#include <string>
bool throwOnAllocate = false;
void * operator new(std::size_t size) {
if (!throwOnAllocate)
if (void * const r = std::malloc(size))
return r;
throw std::bad_alloc();
}
void operator delete(void * ptr) { std::free(ptr); }
int main() {
std::string const errorMessage("OH NOEZ! =(");
throwOnAllocate = true;
throw std::runtime_error(errorMessage);
}
See Question&Answers more detail:os