鉴于make_unique
和make_shared
的可用性,以及unique_ptr
和shared_ptr
析构函数的自动删除,除了支持遗留代码之外,还有哪些情况可供使用C ++ 14中的new
和delete
?
答案 0 :(得分:36)
虽然智能指针在许多情况下比原始指针更可取,但在C ++ 14中仍有许多用于new
/ delete
的用例。
如果您需要编写任何需要就地构造的内容,例如:
您需要使用展示位置new
,可能还需要使用delete
。没办法。
对于您要编写的某些容器,您可能希望使用原始指针进行存储。
对于标准智能指针 甚至,如果您想使用自new
和make_unique
之后的自定义删除工具,则仍需要make_shared
允许这样做。
答案 1 :(得分:7)
使用make_unique
和make_shared
而不是new
的原始调用是一种相对常见的选择。但这不是强制性的。假设您选择遵循该约定,则有几个地方可以使用new
。
首先,非自定义展示位置new
(我将忽略"非自定义"部分,并将其称为展示位置new
)是一张完全不同的卡片游戏比标准(非展示位置)new
。它在逻辑上与手动调用析构函数配对。标准new
都从免费商店获取资源,并在其中构建对象。它与delete
配对,它会破坏对象并将存储回收到免费商店。从某种意义上说,标准new
会在内部调用new
,标准delete
会在内部调用析构函数。
放置new
是您在某些存储上直接调用构造函数的方式,并且是高级生命周期管理代码所必需的。如果您正在实施optional
,对齐存储上的类型安全union
或智能指针(具有统一存储和非统一生命周期,如make_shared
),您将使用展示位置{ {1}}。然后在特定对象的生命周期结束时,直接调用它的析构函数。与非展示位置new
和new
一样,展示位置delete
和手动析构函数调用成对出现。
自定义展示位置new
是使用new
的另一个原因。自定义位置new
可用于从非全局池分配资源 - 作用域分配,或分配到跨进程共享内存页,分配到视频卡共享内存等 - 以及其他用途。如果您要编写使用自定义展示位置新分配内存的new
,则必须使用make_unique_from_custom
关键字。自定义展示位置new
可能就像放置新版本一样(因为它实际上并没有获取资源,而是以某种方式传递资源),或者它可以像标准new
那样行事(因为它获取资源,可能使用传入的参数。)
如果自定义展示位置new
抛出,则会调用自定义展示位置delete
,因此您可能需要编写该展示位置new
。在C ++中,您不能调用自定义展示位置delete
,(C ++)会调用(r overload)。
最后,make_shared
和make_unique
功能不完整,因为它们不支持自定义删除。
如果您正在撰写make_unique_with_deleter
,您仍然可以使用make_unique
分配数据,并.release()
将其添加到您的独特删除小组中。如果您的删除者希望将其状态填充到指向缓冲区而不是unique_ptr
或填充到单独的分配中,那么您需要在此处使用展示位置new
。
对于make_shared
,客户端代码无法访问"引用计数存根"创作代码。据我所知,你不能轻易地同时具有对象和引用计数块的组合分配"和自定义删除器。
此外,只要make_shared
s持续存在,weak_ptr
就会导致对象本身的资源分配(存储)持续存在:在某些情况下,这可能不合适,所以你&# 39; d想要shared_ptr<T>(new T(...))
来避免这种情况。
在您要拨打非展示位置new
的少数情况下,如果您想要与{{1}分开管理,则可以调用make_unique
,然后调用.release()
指针}。这会增加您对资源的RAII覆盖率,并且意味着如果存在异常或其他逻辑错误,您就不太可能泄漏。
我在上面提到过,我不知道如何使用带有共享指针的自定义删除器,该共享指针可以轻松使用单个分配块。这是一个如何巧妙地做到这一点的草图:
unique_ptr
我认为应该这样做。我试图允许无状态删除者使用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
没有机会在custom_delete
构建之前被销毁。
答案 2 :(得分:3)
我能想到的唯一原因是,您可能希望在unique_ptr
或shared_ptr
上使用自定义删除工具。要使用自定义删除器,您需要直接创建智能指针,传递new
的结果。即使这种情况并不常见,但它确实在实践中出现。
除此之外,似乎make_shared
/ make_unique
应涵盖几乎所有用途。
答案 3 :(得分:1)
我想说new
和delete
的唯一原因是实现其他类型的智能指针。
例如,该库仍然没有像boost :: intrusive_ptr一样的侵入性指针,这是一个遗憾,因为它们因性能原因而优于共享指针,正如Andrei Alexandrescu指出的那样。