Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to construct a <stdexcept> or <system_error> exception without throwing?

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);
}
like image 991
jotik Avatar asked Mar 19 '16 20:03

jotik


2 Answers

Short answer, it is not possible to construct any objects with an absolute guarantee of no exceptions.

You may consider allocating on the stack, but your thread stack could run out and will cause system error/exception. Your CPU may get an external interrupt, right when you throw, that system can't handle and everything gets fried. As others have suggested, don't fret the small stuff. Running out of memory is something most user programs can't recover from, so don't worry about it, elegantly fail. Don't try to handle every bad situation, just ones you can easily recover from.

As a side-note, for the situation about running out of memory, a lot of high graphics games do all of their heap allocation up-front during game initialization and try to avoid after the game starts to reduce running into issues with out of memory/slow allocations in the middle of a game (jittery game & bad user experience). You can similarly be smart about design of your program to reduce the chances of running into bad situations.

like image 128
Chintan Avatar answered Oct 23 '22 03:10

Chintan


You cannot construct std::logic_error or std::runtime_error without ever getting std::bad_alloc, but most of the time it doesn't matter. If the operation can fail, separate code path has to be provided anyway to handle this, same code path can be used to handle std::bad_alloc as well. In those rare cases when it matters, you should just be derived from std::exception directly and make what() member function return a fixed string. Most of the time "function X throws Y", should be read as "function X throws Y and std::bad_alloc", unless explicitly stated otherwise. This also why C++11 discarded throw() specified in favor of noexcept().

I don't find the up-front allocation of exceptions helpful, since if you are encountering std::bad_alloc, losing some error information at this point might be a good choice as such situations are rare and not worth the hassle. Calling code can just assume such function has failed due to memory allocation failure as part of its normal work.

Now if you are worried about losing error information because of exception is thrown during handling of another exception, you can try looking at exception chaining (or exception nesting). Examples of this can be found in other languages:

  • Python has the best implementation of this and implements both explicit and implicit exception chaining: https://www.python.org/dev/peps/pep-3134/
  • Java implements explicit and partially implicit chaining: http://docs.oracle.com/javase/7/docs/api/java/lang/Throwable.html (look for getCause() and getSuppressed())
  • C# implements only explicit exception chaining: https://msdn.microsoft.com/en-us/library/system.exception.innerexception(v=vs.110).aspx

C++11 provides standard way of nesting exceptions with std::throw_with_nested() and std::rethrow_if_nested(). Unfortunately, if you throw exception outside of catch() block, using std::rethrow_if_nested() later on that exception will terminate your program, so this facility IMO is kinda broken. If you really care about such issues, you can implement your own variant that can do both explicit and implicit chaining (you will need std::current_exception() to do this). You won't be able to force external libraries to use your facility, but at least your code can be very advanced in this regard.

like image 25
StaceyGirl Avatar answered Oct 23 '22 05:10

StaceyGirl