How to fail a constructor with new(std::nothrow)?

3.6k views Asked by At

Consider the following code:

#include <new>
#include <malloc.h>
#include <stdio.h>

void * operator new(size_t size) {
    void *res;
    if (size == 1) {
        res = NULL;
    } else {
        res = malloc(size);
    }
    fprintf(stderr, "%s(%zu) = %p\n", __PRETTY_FUNCTION__, size, res);
    if (res == NULL) throw std::bad_alloc();
    return res;
}

void * operator new(size_t size, const std::nothrow_t&) {
    void *res;
    if (size == 1) {
        res = NULL;
    } else {
        res = malloc(size);
    }
    fprintf(stderr, "%s(%zu) = %p\n", __PRETTY_FUNCTION__, size, res);
    return res;
}

void operator delete(void *ptr) {
    fprintf(stderr, "%s(%p)\n", __PRETTY_FUNCTION__, ptr);
    free(ptr);
}

void operator delete(void *ptr, const std::nothrow_t&) {
    fprintf(stderr, "%s(%p)\n", __PRETTY_FUNCTION__, ptr);
    free(ptr);
}

class Foo { };

class Bar {
public:
    Bar() : ptr(new Foo()) {
        fprintf(stderr, "%s: ptr = %p\n", __PRETTY_FUNCTION__, ptr);
    }
    Bar(const std::nothrow_t&) noexcept : ptr(new(std::nothrow) Foo()) {
        fprintf(stderr, "%s: ptr = %p\n", __PRETTY_FUNCTION__, ptr);
    }
    ~Bar() noexcept {
        delete ptr;
    }
    Foo *ptr;
};

class Baz {
public:
    Baz() : ptr(new Foo()) {
        fprintf(stderr, "%s: ptr = %p\n", __PRETTY_FUNCTION__, ptr);
    }
    ~Baz() {
        delete ptr;
    }
    Foo *ptr;
};

int main() {
    Bar *bar = new(std::nothrow) Bar(std::nothrow_t());
    if (bar != NULL) {
        delete bar;
    } else { fprintf(stderr, "bad alloc on Bar(std::nothrow_t())\n"); }
    fprintf(stderr, "\n");
    try {
        bar = new(std::nothrow) Bar();
        delete bar;
    } catch (std::bad_alloc) { fprintf(stderr, "bad alloc on Bar()\n"); }
    fprintf(stderr, "\n");
    try {
        Baz *baz = new Baz();
        delete baz;
    } catch (std::bad_alloc) { fprintf(stderr, "bad alloc on Baz()\n"); }
}

This produces the following output:

void* operator new(size_t, const std::nothrow_t&)(8) = 0x1fed010
void* operator new(size_t, const std::nothrow_t&)(1) = (nil)
Bar::Bar(const std::nothrow_t&): ptr = (nil)
void operator delete(void*)((nil))
void operator delete(void*)(0x1fed010)

void* operator new(size_t, const std::nothrow_t&)(8) = 0x1fed010
void* operator new(std::size_t)(1) = (nil)
void operator delete(void*, const std::nothrow_t&)(0x1fed010)
bad alloc on Bar()

void* operator new(std::size_t)(8) = 0x1fed010
void* operator new(std::size_t)(1) = (nil)
void operator delete(void*)(0x1fed010)
bad alloc on Baz()

As you can see allocating the first Bar succeeds despite the allocation of Foo failing. The second allocation of Bar and alloaction of Baz fail properly through the use of std::bad_alloc.

Now my question is: How to make "new(std::nothrow) Bar(std::nothrow_t());" free the memory for Bar and return NULL when Foo fails to allocate? Is dependency inversion the only solution?

2

There are 2 answers

2
Yakk - Adam Nevraumont On BEST ANSWER

Let's suppose you want to be able to have failed construction without exceptions as a general rule.

I will sketch such a system.

template<class Sig>
struct has_creator;
template<class T, class...Args>
struct has_creator<T(Args...)>

this is a traits class that descendes from true_type iff your type T has a static method that matches the signature bool T::emplace_create(T*, Args&&...).

emplace_create returns false on creation failure. The T* must point to an uninitialized chunk of memory with proper alignment and sizeof(T) or larger.

We can now write this:

template<class T, class...Args>
T* create( Args&&... args )

which is a function that detects if T has_creator, and if so allocates memory, does an emplace_create, and if it fails it cleans up the memory and returns nullptr. Naturally it uses nothrow new.

You now use create<T> in place of new everywhere.

The big downside is that we don't support inheritance very well. And composition gets tricky: we basically write our constructor in emplace_create and have our actual constructor do next to nothing, and in emplace_create we handle failure cases (like sub objects having a failed create<X> call).

We also get next to no help with inheritance. If we want help with inheritance, we can write two different methods -- one for a no-failure initial construction, and the second for failure-prone creation of resources.

I will note that it gets a touch less annoying if you stop storing raw pointers anywhere. If you store things in std::unique_ptr everywhere (even to the point of having create<T> returning std::unique_ptr<T>), and throw in a guarded end-of-scope destroyer with abort, and your destructor has to be able to handle "half-constructed" objects.

3
Cheers and hth. - Alf On

C++11 ยง5.3.4/18:

If any part of the object initialization described above terminates by throwing an exception and a suitable deallocation function can be found, the deallocation function is called to free the memory in which the object was being constructed, after which the exception continues to propagate in the context of the new-expression.

So std::nothrow does not guarantee no exception from a new-expression. It’s just an argument passed to the allocation function, selecting the no-throwing one from the standard library. It’s apparently mainly in support of more C-style pre-standard code.

The whole cleanup mechanism in modern C++ is based on exceptions.

To work around that – which I think is silly, not a thing to do, but you’re asking – do e.g.

#include <iostream>
#include <new>
#include <stdexcept>
#include <stdlib.h>         // EXIT_FAILURE
#include <typeinfo>
#include <utility>

namespace my { class Foo; }

template< class Type, class... Args >
auto null_or_new( Args&&... args )
    -> Type*
{
    #ifdef NULLIT
        if( typeid( Type ) == typeid( my::Foo ) ) { return nullptr; }
    #endif

    try
    {
        return new( std::nothrow ) Type( std::forward<Args>( args )... );
    }
    catch( ... )
    {
        return nullptr;
    }
}

namespace my
{
    using namespace std;

    class Foo {};

    class Bah
    {
    private:
        Foo*    p_;

    public:
        Bah()
            : p_( null_or_new<Foo>() )
        {
            clog << "Bah::<init>() reports: p_ = " << p_ << endl;
            if( !p_ ) { throw std::runtime_error( "Bah::<init>()" ); }
        }
    };
}  // namespace my

auto main() -> int
{
    using namespace std;
    try
    {
        auto p = null_or_new<my::Bah>();
        cout << p << endl;
        return EXIT_SUCCESS;
    }
    catch( exception const& x )
    {
        cerr << "!" << x.what() << endl;
    }
    return EXIT_FAILURE;
}

Why the requested approach IMHO is silly:

  • It forsakes the safety of exceptions. No guaranteed cleanup on failure propagation. Indeed no guaranteed failure propagation, it’s all very manual.

  • It discards all information about the failure, e.g. exception message. One can add mechanisms to retain some of that, but it gets complicated and inefficient.

  • It has no plausible advantage that I can think of.


In passing, note that format specifier %zu and macro __PRETTY_FUNCTION__ don’t work with Visual C++.

Also note that in order to return a nullpointer, an allocation function must be declared to be non-throwing.


Addendum

An example of doing things very very manually, avoiding even internal exceptions. Mainly the cost is that one gives up the usual C++ machinery where only those data members already successfully constructed, are destroyed when failure is detected. Instead everything must be constructed to dummy states, so that one has zombie objects temporarily available.

#include <iostream>
#include <new>
#include <stdexcept>
#include <stdlib.h>         // EXIT_FAILURE
#include <typeinfo>
#include <utility>

namespace my { class Foo; }

struct Result_code { enum Enum { success, failure }; };

template< class Type, class... Args >
auto null_or_new( Args&&... args )
    -> Type*
{
    #ifdef NULLIT
        if( typeid( Type ) == typeid( my::Foo ) ) { return nullptr; }
    #endif

    auto code = Result_code::Enum();
    auto const p = new( std::nothrow ) Type( code, std::forward<Args>( args )... );
    if( p != nullptr && code != Result_code::success )
    {
        p->Type::~Type();
        ::operator delete( p, std::nothrow );
        return nullptr;
    }
    return p;
}

namespace my
{
    using namespace std;

    class Foo { public: Foo( Result_code::Enum& ) {} };

    class Bah
    {
    private:
        Foo*    p_;

    public:
        Bah( Result_code::Enum& code )
            : p_( null_or_new<Foo>() )
        {
            clog << "Bah::<init>() reports: p_ = " << p_ << endl;
            if( !p_ ) { code = Result_code::failure; }
        }
    };
}  // namespace my

auto main() -> int
{
    using namespace std;
    try
    {
        auto p = null_or_new<my::Bah>();
        cout << p << endl;
        return EXIT_SUCCESS;
    }
    catch( exception const& x )
    {
        cerr << "!" << x.what() << endl;
    }
    return EXIT_FAILURE;
}