新增和删除在C ++ 14中仍然有用吗?
鉴于make_unique
和make_shared
可用性,以及unique_ptr
和shared_ptr
析构函数的自动删除,在C ++ 14中使用new
和delete
的情况是什么(除了支持遗留代码)呢?
在许多情况下,智能指针比原始指针更可取,但在C ++ 14中仍然有很多new
/ delete
的用例。
如果您需要编写任何需要就地构build的内容,例如:
- 一个内存池
- 一个分配器
- 一个标记的变体
- 二进制消息到缓冲区
你将需要使用new
位置,可能还需要delete
。 没办法。
对于您想要编写的某些容器,您可能需要使用原始指针进行存储。
即使对于标准的智能指针,如果你想使用自定义的删除器,你仍然需要new
,因为make_unique
和make_shared
不允许这样做。
使用make_unique
和make_shared
是比较常见的select,而不是对new
raw调用。 但是,这不是强制性的。 假设你select遵循这个惯例,有几个地方可以使用new
。
首先,非定制的放置new
(我会忽略“非定制”的一部分,只是把它称为放置new
)是一个完全不同的纸牌游戏比标准(非放置) new
。 它在逻辑上与手动调用析构函数配对。 标准new
都从免费商店获取资源,并在其中构build一个对象。 它与delete
配对,销毁对象并将存储回收到免费商店。 从某种意义上说,标准的new
调用放置在内部是new
,标准的delete
在内部调用析构函数。
Placement new
是您在某些存储上直接调用构造函数的方式,并且是高级生命周期pipe理代码所必需的。 如果您正在实现optional
alignment存储上的types安全union
,或智能指针(具有统一存储和非统一生命周期,如make_shared
),则将使用new
位置。 然后在特定对象的生命周期结束时,直接调用它的析构函数。 就像非放置new
和delete
,放置new
和手动的析构函数调用是成对的。
自定义展示位置new
是另一个使用new
原因。 自定义布局new
可用于从非全局池分配资源,或分配到跨进程共享内存页面,分配到video卡共享内存等等 – 以及其他目的。 如果你想写make_unique_from_custom
来使用自定义位置new来分配它的内存,你必须使用new
关键字。 自定义布局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
,客户端代码无权访问“引用计数存根”创build代码。 据我可以告诉你不容易都有“对象和引用计数块的组合分配”和一个自定义删除器。
另外,只要weak_ptr
持久化, make_shared
就会导致对象自身的资源分配(存储):在某些情况下这可能是不可取的,所以你需要做一个shared_ptr<T>(new T(...))
来避免这一点。
在less数情况下,如果您要独立于unique_ptr
进行pipe理,可以调用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
来使用不起来的空间,但我可能已经搞砸了。
在一个库质量的解决scheme中,如果T::T(Ts...)
被noexcept
,我可以删除bCreated
开销,因为在构造T
之前不会有一个custom_delete
被销毁的机会。
我能想到的唯一原因是偶尔您可能希望使用unique_ptr
或shared_ptr
的自定义删除程序。 要使用自定义删除器,您需要直接创build智能指针,并传递new
的结果。 即使这不是很频繁,但它确实出现在实践中。
除此之外, make_shared
/ make_unique
似乎应该涵盖几乎所有的用途。
我会说new
和delete
的唯一原因是实现其他types的智能指针。
例如,该库仍然没有插入指针作为boost :: intrusive_ptr,这是遗憾的,因为它们在性能方面优于共享指针,正如Andrei Alexandrescu所指出的那样。