How does the constructor fail with a 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 leads to 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, highlighting the first bar succeeds even though Foo doesn't work. The second allocation Bar and alloaction Baz fails with std :: bad_alloc.

Now my question is, how do I create a "new (std :: nothrow) Bar (std :: nothrow_t ()); free memory for the bar and return NULL when Foo fails to allocate? Is dependency inversion the only solution?"

+3


source to share


2 answers


Let's say you want to have a bad design with no exceptions as a general rule.

I will draw such a system.

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

      

it is a trait class that comes with true_type

if your type T

has a static method that matches the signature bool T::emplace_create(T*, Args&&...)

.

emplace_create

returns false on creation failure. T*

must point to an uninitialized chunk of memory with correct alignment sizeof(T)

or greater.

We can now write this:



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

      

which is a function that detects what T

has_creator

, and if so allocates memory, does emplace_create

, and if it doesn't work, it clears the memory and returns nullptr

. Naturally, it uses nothrow new

.

Now you are using create<T>

instead new

.

The big drawback is that we don't support inheritance very well. And composition gets tricky: we basically write our constructor in emplace_create

and do almost nothing, our actual constructor, and emplace_create

handle failures (for example, sub-objects with a failed call create<X>

).

We also don't get any help with inheritance. If we need help with inheritance, we can write two different methods: one for the initial construction without failures, and the other for refusing to create resources.

I will note that it becomes less annoying if you stop storing the original pointers anywhere. If you store things in std::unique_ptr

all over the place (even before it create<T>

returns std::unique_ptr<T>

) and throw away a defended destroyer with an interrupt-terminated trailing limit and your destructor should be able to handle "half-inline" objects.

0


source


C ++ 11 ยง5.3.4 / 18:

" If any part of the object initialization described above completes by throwing an exception and a suitable dealolation function can be found, the deallocation function is called freeing the memory in which the object is, after which the exception continues to propagate in the context of the new expression.

So it std::nothrow

doesn't guarantee an exception from the new expression. It's just an argument passed to the distribution function, choosing non-throwing from the standard library. This seems to be mainly in support of more standard C-style code.

The entire cleanup mechanism in modern C ++ is exception-based.

To get around this: ndash; which I think is stupid, not a thing to do, but you & rsquo; re ask - for example,

#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 is the requested approach IMHO silly:



  • It leaves the safety of exceptions. No guaranteed cleanup on propagation failure. In fact, there is no guaranteed spread of dips, it is all very manual.

  • Throws away all information about the failure, eg. exception message. It is possible to add mechanisms to keep some of them, but it becomes complicated and ineffective.

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


As you go through, note that the format specifier %zu

and the __PRETTY_FUNCTION__

don & rsquo; t work with Visual C ++.

Also note that in order to return a null pointer, the selection function must be declared as non-cast.


Adding

The example is very manual, avoiding even internal exceptions. Basically the cost is that you abandon the usual C ++ mechanism where only data that has already been successfully created is destroyed when a failure is detected. Instead, everything should be created for dummy states, so you have zombie objects .

#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;
}

      

+2


source







All Articles