使用make_unique 和make_shared 而不是对new 的原始调用是一种相对常见的选择。然而,这不是强制性的。假设您选择遵循该约定,则有几个地方可以使用 new。
首先,非自定义放置new(我将忽略“非自定义”部分,并称之为放置new)与标准(非放置)new完全不同的纸牌游戏.它在逻辑上与手动调用析构函数配对。标准new 都从免费存储中获取资源,并在其中构造一个对象。与delete配对,销毁对象并将存储回收到空闲存储。从某种意义上说,标准new在内部调用placementnew,标准delete在内部调用析构函数。
Placement new 是您在某些存储上直接调用构造函数的方式,并且是高级生命周期管理代码所必需的。如果您正在实现optional、对齐存储上的类型安全union,或智能指针(具有统一存储和非统一生命周期,如make_shared),您将使用放置new。然后在特定对象的生命周期结束时,直接调用它的析构函数。与非放置 new 和 delete 一样,放置 new 和手动析构函数调用成对出现。
自定义展示位置new 是使用new 的另一个原因。自定义放置new 可用于从非全局池中分配资源——范围分配,或分配到跨进程共享内存页面,分配到视频卡共享内存等——以及其他用途。如果您想编写make_unique_from_custom,使用自定义位置new 分配其内存,则必须使用new 关键字。自定义放置new 可以像placement new(因为它实际上并不获取资源,而是以某种方式传入资源),或者它可以像标准new 一样(因为它获取资源,可能使用传入的参数)。
如果自定义展示位置new 抛出,则调用自定义展示位置delete,因此您可能需要编写该代码。在 C++ 中,您不会调用自定义放置 delete,它 (C++) 会调用您(r 重载)。
最后,make_shared 和 make_unique 是不完整的函数,因为它们不支持自定义删除器。
如果您正在编写make_unique_with_deleter,您仍然可以使用make_unique 来分配数据,并使用.release() 将其交由您的unique-with-deleter 处理。如果您的删除器想要将其状态填充到指向缓冲区而不是 unique_ptr 或单独的分配中,则需要在此处使用放置 new。
对于make_shared,客户端代码无权访问“引用计数存根”创建代码。据我所知,你不能轻易地同时拥有“对象和引用计数块的组合分配”和自定义删除器。
此外,make_shared 会导致对象本身的资源分配(存储)持续,只要weak_ptrs 持续存在:在某些情况下,这可能是不可取的,所以你想这样做shared_ptr<T>(new T(...)) 来避免这种情况。
在少数情况下,如果您想与unique_ptr分开管理,您可以调用make_unique,然后调用指针.release()。这增加了您的 RAII 资源覆盖率,并意味着如果有异常或其他逻辑错误,您不太可能泄漏。
我在上面提到过,我不知道如何使用自定义删除器和共享指针来轻松使用单个分配块。以下是如何巧妙地做到这一点的草图:
template<class T, class D>
struct custom_delete {
std::tuple<
std::aligned_storage< sizeof(T), alignof(T) >,
D,
bool
> data;
bool bCreated() const { return std::get<2>(data); }
void markAsCreated() { std::get<2>()=true; }
D&& d()&& { return std::get<1>(std::move(data)); }
void* buff() { return &std::get<0>(data); }
T* t() { return static_cast<T*>(static_cast<void*>(buff())); }
template<class...Ts>
explicit custom_delete(Ts...&&ts):data(
{},D(std::forward<Ts>(ts)...),false
){}
custom_delete(custom_delete&&)=default;
~custom_delete() {
if (bCreated())
std::move(*this).d()(t());
}
};
template<class T, class D, class...Ts, class dD=std::decay_t<D>>
std::shared_ptr<T> make_shared_with_deleter(
D&& d,
Ts&&... ts
) {
auto internal = std::make_shared<custom_delete<T, dD>>(std::forward<D>(d));
if (!internal) return {};
T* r = new(internal->data.buff()) T(std::forward<Ts>(ts...));
internal->markAsCreated();
return { internal, r };
}
我认为应该这样做。我试图通过使用tuple 来允许无状态删除器使用任何空间,但我可能搞砸了。
在库质量的解决方案中,如果 T::T(Ts...) 是 noexcept,我可以删除 bCreated 开销,因为在 T 之前没有机会必须销毁 custom_delete构建。