make_unique 的异常感知 malloc 版本

Exception-aware malloc version of make_unique

C++11 中没有 make_unique(不能使用 C++14),尽管可以像这样快速模拟(已经有很多答案建议类似这个):

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

如果期望的结果是在 typename T 上调用 new / delete,这很好。在我的例子中,这并不是因为我覆盖了全局 new / delete,它在内部将(在方便的地方)使用 unique_ptr。因此,我使用 malloc 和基于 malloc 的各种包装器和分配器来使 STL 容器在使用站点没有额外膨胀的情况下工作。在出现异常之前,我用这种方法做得很好......

到目前为止,在 this question 中的答案的基础上,我将此作为可能的通用解决方案:

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

这似乎没问题,但这里完全忽略了关于 new (ptr) T(...) 如何工作的异常处理,而不是 new T(...),以及这如何影响 make_unique 的任何版本,尤其是当使用自定义分配方法。

首先,我知道抛出 std::bad_alloc 是不明智的,但是我相信最小意外原则适用于此,效仿 new 的做法是可以原谅的(即抛出当分配失败而不是返回 nullptr 时)。这就引出了两个问题。

1.用throw std::bad_alloc()代替return { nullptr }是否合理?

2。要正确复制new T(...)的行为,如果构造函数抛出则需要捕获异常,以便立即释放内存并然后重新抛出构造函数异常?

假设两者都是肯定的,下面是正确处理这种情况还是有其他需要考虑的地方?

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?
}

编辑 - 请注意,为简单起见,我忽略了对齐。

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

我觉得还不错。

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(如果你没有)和allocate_unique