Search code examples
c++c++11constructornew-operatornothrow

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


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?


Solution

  • 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.