是新的,删除在 C++14 中仍然有用
Are new and delete still useful in C++14?
鉴于make_unique
和make_shared
的可用性,以及unique_ptr
和shared_ptr
析构函数的自动删除,在C++14中使用new
和delete
的情况(除了支持遗留代码)是什么?
虽然在许多情况下,智能指针比原始指针更可取,但在 C++14 中仍然有很多用于 new
/delete
的用例。
如果需要编写任何需要就地构造的内容,例如:
- 内存池
- 分配器
- 带标记的变体
- 二进制消息到缓冲区
您将需要使用放置new
,并且可能需要使用delete
。没办法。
对于要写入的某些容器,可能需要使用原始指针进行存储。
即使对于标准的智能指针,如果您想使用自定义删除器,您仍然需要new
,因为make_unique
和make_shared
不允许这样做。
使用 make_unique
和 make_shared
而不是对 new
进行原始调用是相对常见的选择。 然而,这不是强制性的。 假设您选择遵循该约定,则有几个地方可以使用new
。
首先,非自定义放置new
(我将忽略"非自定义"部分,仅称其为放置new
)是与标准(非放置)new
完全不同的纸牌游戏。 它在逻辑上与手动调用析构函数配对。 标准new
既从免费存储中获取资源,又在其中构造对象。 它与 delete
配对,这会破坏对象并将存储回收到免费存储。 从某种意义上说,标准new
内部调用放置new
,标准delete
在内部调用析构函数。
放置new
是在某些存储上直接调用构造函数的方式,是高级生存期管理代码所必需的。 如果要实现 optional
,对齐存储上的类型安全union
,或智能指针(具有统一存储和非统一生存期,如 make_shared
),您将使用放置new
。 然后,在特定对象的生存期结束时,直接调用其析构函数。 与非放置new
和delete
一样,放置new
和手动析构函数调用成对出现。
自定义放置new
是使用new
的另一个原因。 自定义放置new
可用于从非全局池分配资源 - 作用域分配,或分配到跨进程共享内存页,分配到视频卡共享内存等 - 以及其他目的。 如果要编写使用自定义放置 new 分配内存的make_unique_from_custom
,则必须使用 new
关键字。 自定义放置new
可以像新放置一样(因为它实际上并不获取资源,而是以某种方式传入资源),或者它可以像标准new
一样(因为它获取资源,可能使用传入的参数)。
如果抛出自定义放置new
,则会调用自定义放置delete
,因此您可能需要编写该放置位置。 C++你不调用自定义放置delete
,它(C++)调用你(r重载)。
最后,make_shared
和make_unique
是不完整的函数,因为它们不支持自定义删除程序。
如果您正在编写make_unique_with_deleter
,您仍然可以使用 make_unique
来分配数据,并将其.release()
到您唯一删除程序的维护中。 如果删除程序想要将其状态填充到指向缓冲区中,而不是填充到unique_ptr
或单独的分配中,则需要在此处使用放置new
。
对于make_shared
,客户端代码无法访问"引用计数存根"创建代码。 据我所知,您不能轻易地同时拥有"对象和引用计数块的组合分配"和自定义删除器。
此外,make_shared
会导致对象本身的资源分配(存储)持续存在,只要weak_ptr
持续存在:在某些情况下,这可能不是可取的,因此您需要执行shared_ptr<T>(new T(...))
以避免这种情况。
在少数情况下,如果要调用非放置new
,可以调用 make_unique
,如果要与该unique_ptr
分开管理,则可以.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
。
我能想到的唯一原因是,偶尔您可能希望在unique_ptr
或shared_ptr
中使用自定义删除器。要使用自定义删除器,您需要直接创建智能指针,传入new
的结果。即使这种情况并不常见,但在实践中确实会出现。
除此之外,似乎make_shared
/make_unique
应该涵盖几乎所有用途。
我会说new
和delete
的唯一原因是实现其他类型的智能指针。
例如,库仍然没有像 boost::intrusive_ptr 这样的侵入性指针,这很遗憾,因为它们在性能方面优于共享指针,正如 Andrei Alexandrescu 指出的那样。
- 枚举环境变量的惯用C++14/C++17方法
- 为什么需要复制构造函数,在哪些情况下它们非常有用
- 其中降频广播实际上是有用的
- 错误的自动扣除 c++14
- 访问和打印元组中的数据,并使用 C++14 使用模板函数显示数据
- C++14 中unordered_map矢量和擦除删除成语的奇怪行为
- 构建 TensorFlow r1.14 C++文件时缺少文件"tensorflow/core/framework/types.pb.h"
- 如何在 C++14 中处理此特定代码
- 如何使用MSVC 2019创建和使用Qt 5.14.0自定义小工具插件
- 编译器现在遵循C++14标准,我的项目不再编译神秘的SFML错误
- C++14 : 2 个随机生成器 - 一个有效,另一个无效
- 既然我们有内联变量,extern const 还有用吗?
- 在 C++14 中手动实现结构化绑定
- 如何范围检查 C++14 个用户定义的文本?
- 使用 lambda 表达式的 Raspbian G++ 8.3.0 导致 ']' 之前的预期主表达式 - 即使标准设置为 c++14
- 在向量 C++14(无限制联合)的结构内的联合中创建和存储字符串
- C++14 遇到奇怪的"use of deleted function"错误
- 下面描述的概念如何在C++14中定义?
- 在编译时解压缩数组扩展数据块 (C++11/14)
- 是新的,删除在 C++14 中仍然有用