4

In C++11 there is no make_unique (can't use C++14), although one can quickly be mocked up like so (plenty of answers already out there suggest something like this):

template< typename T, typename ... Args >
std::unique_ptr< T > my_make_unique (Args && ... args) {
  return { new T( std::forward< Args >( args )... ) };
}

This is fine if the desired result is for new / delete to be invoked on typename T. In my case it's not because I'm overriding global new / delete, which internally will (where convenient) use unique_ptr. So instead I'm using malloc and various wrappers & allocators based on malloc to make STL containers work without extra bloat at the site of usage. I was doing fine with this approach until exceptions came along...

Building on the answers in this question so far I have this as a possible generic solution:

template< typename T >
struct destroy_free {
  void operator() (void * p) {
    if ( !p ) return;
    static_cast< T* >( p )->~T();
    free( p );
  }
};
template< typename T, typename ... Args >
auto malloc_make_unique (Args && ... args) -> std::unique_ptr< T, destroy_free< T > > {
  if ( auto ptr = malloc( sizeof(T) ) )
    return { new ( ptr ) T( std::forward< Args >( args )... ) };
  return { nullptr }; // global new would throw bad_alloc here.
}

This seems ok but exception handling is totally ignored here with regard to how new (ptr) T(...) would work, as opposed to new T(...), and how this affects any version of make_unique especially when using a custom allocation method.

For a start, I'm aware throwing std::bad_alloc is ill advised, however I believe the principle of least surprise applies here and it's forgivable to emulate what new would do (ie, throw when allocation fails as opposed to returning nullptr). This begs two questions.

1. Is it justifiable to replace return { nullptr } with throw std::bad_alloc()?

2. To correctly replicate new T(...)'s behavior, if the constructor throws then the exception needs to be caught, so the memory can be released immediately and then rethrow the constructor exception?

Assuming yes to both, is the below handling the situation correctly or is there anything else to consider?

template< typename T, typename ... Args >
auto malloc_make_unique_v2 (Args && ... args) -> std::unique_ptr< T, destroy_free< T > > {
  if ( auto ptr = malloc( sizeof(T) ) ) try {
    return { new ( ptr ) T( std::forward< Args >( args )... ) };
  } catch( ... ) { // catch constructor exceptions
    // assumed: memory allocated but object not constructed
    free( ptr ); // release memory, object was not constructed?
    throw; // propagate whatever the exception was during construction?
  }
  throw std::bad_alloc(); // no memory allocated, throw bad_alloc?
}

edit - please note I'm ignoring alignment for simplicity.

Community
  • 1
  • 1
Xeren Narcy
  • 875
  • 5
  • 15
  • 1
    It is a design decision whether your function throws or returns `nullptr` on error. But the notion of throwing on some errors and returning `nullptr` for others - which your first version does - will confuse/annoy users of your code. – Peter Apr 24 '17 at 09:08

1 Answers1

0

Assuming yes to both, is the below handling the situation correctly or is there anything else to consider?

Looks ok to me.

There is std::allocate_shared which creates shared_ptr with object memory allocated by custom allocator. What you are making is essentially allocate_unique which does not exist (perhaps, yet) in standard c++. Maybe you could create custom allocator that uses malloc/free and then implement your own make_unique (if you don't have it) and allocate_unique.

Pavel P
  • 15,789
  • 11
  • 79
  • 128