智能指针如何在delete和delete[]之间做出选择?

34

考虑:

delete new std :: string [2];
delete [] new std :: string;

众所周知,第一个是错误的。如果第二个不是错误的,我们就不需要两个不同的运算符了。

现在考虑:

std :: unique_ptr <int> x (new int [2]);
std :: unique_ptr <int> y (new int);

x 是否知道使用 delete[] 而不是 delete


背景:当我想到指针的数组类型限定符可能是一种方便的语言特性时,这个问题浮现在我的脑海中。

int *[] foo = new int [2]; // OK
int *   bar = new int;     // OK
delete [] foo;             // OK
delete bar;                // OK
foo = new int;             // Compile error
bar = new int[2];          // Compile error
delete foo;                // Compile error
delete [] bar;             // Compile error

1
不要修改数组类型限定符(已经存在)以按照您所示的方式工作,最好的方法是消除所有导致数组问题的隐式类型转换。例如,new int [2] 应该返回类型为 int (*)[2] 的值。无论如何,现在修复本地数组的所有问题为时已晚,但您可以使用 std::array 并避免处理它们。std::array 的工作方式类似于本地数组应该工作的方式,您永远不必使用 new[]delete[] - bames53
@bames53,std::array 有一个限制。你可以使用 new int[n],但是你不能使用 std::array<int,n>(除非在编译时已知 n)。但是,我仍然避免使用原生数组!# - Aaron McDaid
@AaronMcDaid 如果你需要动态分配,可以使用 std::vector - bames53
5个回答

32
很不幸,他们不知道该使用哪种删除方式,因此他们使用了delete。这就是为什么对于每个智能指针,我们都有一个智能数组对应物的原因。
std::shared_ptr uses delete
std::shared_array uses delete[]

那么,你的行

std :: unique_ptr <int> x (new int [2]);

实际上会导致未定义的行为。
顺便说一下,如果你写成:
std :: unique_ptr<int[]> p(new int[2]);
                     ^^

由于您明确请求,因此将使用delete[]。但是,以下行仍将是未定义行为。

std :: unique_ptr<int[]> p(new int);

他们不能选择使用delete还是delete[]的原因是new intnew int[2]完全相同类型-int*
Base没有虚析构函数的情况下,正确使用删除器的相关问题涉及到使用smart_ptr<void>smart_ptr<Base>这里有一个相关的问题。

6
因为类型完全相同! - Armen Tsirunyan
6
(new int)(new int [2])都是类型为int*的指针,模板特化无法区分它们。 - Fred Foo
2
typedef <typename T> using shared_array = shared_ptr<T[]>; - spraff
3
不,shared_ptr没有针对T[]的特化。 - Xeo
3
谈论不存在的 std::shared_array - Nicol Bolas
显示剩余8条评论

6

没有“神奇”的方法可以检测 int* 是指向:

  • 单个堆分配的整数
  • 堆分配的数组
  • 在堆分配的数组中的整数

这些信息被类型系统丢失,也没有运行时方法(可移植的)可以修复。这是C语言中一个令人恼火的,严重的设计缺陷 (*),C++也继承了这个问题(一些人认为这是为了兼容性而做出的决定)。

然而,在智能指针中处理数组有一些方法。

首先,你的unique_ptr类型不正确,不能用于处理数组,你应该使用:

std::unique_ptr<int[]> p(new int[10]);

这段代码旨在调用delete[]。我知道有谈论在Clang中实现特定警告以捕获与unique_ptr明显不匹配的情况:这是一个实现问题(标准仅表示它是UB),并且并非所有情况都可以在没有WPA的情况下覆盖。

其次,boost::shared_ptr可以具有自定义删除器,如果您将其设计为调用正确的delete[]运算符,则可以使用该删除器。但是,有一个专门针对此问题设计的boost::shared_array。一次又一次地,检测不匹配是一个实现质量问题。std::shared_ptr存在相同的问题(在ildjarn的备注后进行了编辑)。

我同意这不是很美观。似乎如此令人讨厌,以至于来自C起源的设计缺陷 (*) 今天仍在困扰着我们。

(*)一些人会说,C倾向于避免开销,这将增加开销。但是,毕竟malloc始终知道块的大小。


2
C语言没有函数重载和模板,相比C++来说并不真正需要像C++那样严格的类型区分。 - dan04
1
@MatthieuM。我同意在C++中这不是问题,而且在C语言中拥有一个适当的数组类型将有助于消除许多错误,但在系统语言中,您需要能够访问原始内存区域,就像它们是arrays一样,并且无需嵌入可能不适合硬件的内存布局的size。现在,这并不意味着不能有array类型和buffer(或raw_array)类型。然而,您可能需要复制其他类似的代码来处理这两个变体...我不是在争论,只是提出潜在的设计问题。 - David Rodríguez - dribeas
@DavidRodríguez-dribeas:是的,我理解直接映射到原始内存的担忧。但我认为这可以通过定义一个范围结构来轻松处理,该结构具有大小和指针或类似于 C++ 半开区间的两个指针。然后您可以自由地根据该接口制作您的函数 :) - Matthieu M.
1
@Matthieu 你犯了一个假设错误,认为 C 的目的是成为一种高级语言,可以保护你免受糟糕编程带来的后果。但事实并非如此。C 的存在是为了使编写低级代码不那么痛苦;C 中的某些极其恶劣的操作是必需的,以使复杂应用程序正常工作。(特别是在较小的机器上,开销的成本可能意味着能否将其放入内存) - Donal Fellows
@ildjarn:没错,我也是这么认为的,但回过头来看,这可能并不容易:如果有人在数组内部抓取了一个引用呢?(当前使用指针)。 - Matthieu M.
显示剩余9条评论

3

来自微软文档

unique_ptr<Type[]>的部分特化管理使用new[]分配的数组对象,并具有默认删除器default_delete<Type[]>,其专用于调用delete[] _Ptr

我添加了最后两个方括号,因为没有它们似乎是一个错别字,所以没有意义。


阅读Armen Tsirunyan的回答,我不知道该怎么看待这个问题。我会让它保持原样,作为一个有趣的观点。也许我错过了一些基本的东西。 - unwind
我认为MSDN的意思是,如果你写了shared_ptr<int[]> p(new int[2]),那么将使用delete[]。但是,如果你写了shared_ptr<int> p (new int[2]),这将导致未定义行为。 - Armen Tsirunyan
我想你可以编写像 shared_ptr<int>i=shared_ptr<int>::allocate()shared_ptr<int[]>i=shared_ptr<int[]>::allocate(n) 这样的代码,并禁止使用原始指针进行构造。(指的是自己的shared_ptr - spraff

3

std::unique_ptr并不适用于数组,最新的boost文档中有如下说明:

通常情况下,shared_ptr无法正确地保存指向动态分配数组的指针。可以使用shared_array来实现该功能。

如果您需要对指针数组进行内存管理,则可以根据您的要求选择以下几种选项:

  1. 使用boost::shared_array
  2. 使用std::vectorboost::shared_ptr
  3. 使用boost指针容器,例如boost::ptr_vector

жҲ‘жғіжҲҗе‘ҳеҮҪж•°operator[](size_t)е’Ңoperator*()зҡ„дә’ж–ҘеҸҜиғҪжҳҜдёҖдёӘеҫҲеҘҪзҡ„жҸҗзӨәгҖӮ - spraff
你用shared_ptr的话来支持关于unique_ptr的主张,这有点奇怪。 - sellibitze
std::unique_ptr 专门为数组类型进行了特化,如果您指定了 delete[](例如 std::unique_ptr<int[]>),它将使用该操作符。但是,std::vector 是更好的选择。我不认为有必要使用 shared_array - bames53

0

Nicolai Josuttis ⟶ 请注意,std::shared_ptr 提供的默认删除器调用的是 delete 而不是 delete[]。这意味着默认删除器仅适用于共享指针拥有使用 new 创建的单个对象的情况。不幸的是,为数组创建 std::shared_ptr 是可能的,但是是错误的:

std::shared_ptr<int>(new int[10]) //error but compiles

如果您使用new[]创建对象数组,则必须定义自己的删除器。例如:

std::shared_ptr<int> ptr(new int[10], 
                         [](int* p){ delete[] p; });

或者

std::shared_ptr<int> ptr(new int[10], 
                         std::default_delete<int[]>());

对我来说,这是最好的解决方案!


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接