【发布时间】:2017-04-18 12:31:10
【问题描述】:
在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?
}
编辑 - 请注意,为了简单起见,我忽略了对齐。
【问题讨论】:
-
函数是否抛出或返回
nullptr是一个设计决定。但是抛出一些错误并为其他人返回nullptr的概念——你的第一个版本就是这样做的——会让你的代码的用户感到困惑/烦恼。
标签: c++11 exception-handling malloc unique-ptr placement-new