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.