Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Any pitfalls with allocating exceptions on the heap?

Tags:

c++

exception

Question says it all: Are there any pitfalls with allocating exceptions on the heap?

I am asking because allocating exceptions on the heap, in combination with the polymorphic exception idiom, solve the problem of transporting exceptions between threads (for the sake of discussion, assume that I can't use exception_ptr). Or at least I think it does...

Some of my thoughts:

  • The handler of the exception will have to catch the exception and know how to delete it. This can be solved by actually throwing an auto_ptr with the appropriate deleter.
  • Are there other ways to transport exceptions across threads?
like image 386
TripShock Avatar asked Feb 23 '23 18:02

TripShock


2 Answers

Are there any pitfalls with allocating exceptions on the heap?

One obvious pitfall is that a heap allocation might fail.

Interestingly, when an exception is thrown it actually throws a copy of the exception object that is the argument of throw. When using gcc, it creates that copy in the heap but with a twist. If heap allocation fails it uses a static emergency buffer instead of heap:

extern "C" void *
__cxxabiv1::__cxa_allocate_exception(std::size_t thrown_size) throw()
{
    void *ret;

    thrown_size += sizeof (__cxa_refcounted_exception);
    ret = malloc (thrown_size);

    if (! ret)
    {
        __gnu_cxx::__scoped_lock sentry(emergency_mutex);

        bitmask_type used = emergency_used;
        unsigned int which = 0;

        if (thrown_size > EMERGENCY_OBJ_SIZE)
            goto failed;
        while (used & 1)
        {
            used >>= 1;
            if (++which >= EMERGENCY_OBJ_COUNT)
                goto failed;
        }

        emergency_used |= (bitmask_type)1 << which;
        ret = &emergency_buffer[which][0];

    failed:;

        if (!ret)
            std::terminate ();
    }
}

So, one possibility is to replicate this functionality to protect from heap allocation failures of your exceptions.

The handler of the exception will have to catch the exception and know how to delete it. This can be solved by actually throwing an auto_ptr with the appropriate deleter.

Not sure if using auto_ptr<> is a good idea. This is because copying auto_ptr<> destroys the original, so that after catching by value as in catch(std::auto_ptr<std::exception> e) a subsequent throw; with no argument to re-throw the original exception may throw a NULL auto_ptr<> because it was copied from (I didn't try that).

I would probably throw a plain pointer for this reason, like throw new my_exception(...) and catch it by value and manually delete it. Because manual memory management leaves a way to leak memory I would create a small library for transporting exceptions between threads and put such low level code in there, so that the rest of the code doesn't have to be concerned with memory management issues.

Another issue is that requiring a special syntax for throw, like throw new exception(...), may be a bit too intrusive, that is, there may be existing code or third party libraries that can't be changed that throw in a standard manner like throw exception(...). It may be a good idea just to stick to the standard throw syntax and catch all possible exception types (which must be known in advance, and as a fall-back just slice the exception and only copy a base class sub-object) in a top-level thread catch block, copy that exception and re-throw the copy in the other thread (probably on join or in the function that extracts the other's thread result, although the thread that throws may be stand-alone and yield no result at all, but that is a completely another issue and we assume we deal with some kind of worker thread with limited lifetime). This way the exception handler in the other thread can catch the exception in a standard way by reference or by value without having to deal with the heap. I would probably choose this path.

You may also take a look at Boost: Transporting of Exceptions Between Threads.

like image 173
Maxim Egorushkin Avatar answered Mar 03 '23 06:03

Maxim Egorushkin


There are two evident one: One - easy - is that doing throw new myexception risk to throw a bad_alloc (not bad_alloc*), hence catch exception* doesn't catch the eventual impossible to allocate exception. And throwing new(nothrow) myexception may throw ... a null pointer.

Another - more a design issue - is "who has to catch". If it is not yourself, consider a situation where your client, that may also be a client of somebody else, has - depending on who's throwing - to decide if delete or not. May result in a mess.

A typical way to solve the problem is throwing a static variable by reference (or address): doesn't need to be deleted and doesn't require to be copied when going down in the unrolled stack

like image 25
Emilio Garavaglia Avatar answered Mar 03 '23 07:03

Emilio Garavaglia