析构函数 - 我应该使用delete还是delete[]?

7

我正在编写一个模板类,它以指针作为输入并存储它。该指针指向由另一个类分配的对象,并交给包含该类的指针。

现在我想为这个容器创建一个析构函数。我应该如何释放该指针指向的内存?我无法事先知道它是数组还是单个元素。

我对C ++还比较新,请见谅。我一直在使用C,Java是我选择的面向对象语言,但由于想要学习C ++和项目的速度要求,我选择了C ++。

将容器从模板更改为抽象类的容器,可以实现自己的析构函数,这是一个更好的主意吗?


3
JonH的答案是正确的,因此也许你应该提供模板:一个用于数组,另一个不用。另一个答案是避免使用数组,而是期望一个单一的实例,它可能是或可能不是一个自我清理的正确集合,例如vector<>。 - Steven Sudit
1
@Steven Sudit:我认为你应该把那个评论变成一个答案。 - Fred Larson
你可能会在这种方法中遇到线程问题 - 你不能删除在不同线程中分配的东西。 - ChrisF
1
此外,@Alex:如果你是C++的新手,尝试找到干净易懂的方法可能会很困难。通常最好的方法是直接说“我正在调用delete,所以不要给我数组”,而不是试图让它同时适用于两种情况。理想情况下,人们应该使用vector而不是原始内存数组。 - GManNickG
@GMan - 啊,那是几年前我遇到的问题,所以可能是跨进程而不仅仅是跨线程。 - ChrisF
显示剩余3条评论
11个回答

6
如果您不知道它是否使用了 new 或者 new[] 进行了分配,那么删除它是不安全的。
您的代码可能看起来可行。例如,在我工作的某个平台上,只有在具有析构函数的对象数组时才会有区别。所以,您可以这样做:
// by luck, this works on my preferred platform
// don't do this - just an example of why your code seems to work
int *ints = new int[20];
delete ints;

但是你可以这样做:

// crashes on my platform
std::string *strings = new std::string[10];
delete strings;

1
这不是运气,而是因为 int 没有析构函数。 - Steven Sudit
5
@Steven:这是运气问题。使用不匹配的 newdelete 会导致未定义的行为,即使类型有一个平凡的析构函数也是如此。我完全可以根据需要替换 operator newoperator delete;而且我可以期望我的 delete 函数中的指针已经是对应的 new 输出的。 - GManNickG
3
“@Steven - 这是幸运的,因为平台 选择 在类型没有析构函数时优化数组分配。但另一个平台可能会以不同的方式处理这个问题,那么这段代码在另一个平台上就会出现问题。” - R Samuel Klatchko
我不想让它听起来像是我在暗示我们可以依赖于简单类型总是有效,所以让我重新表述我的答案:这是运气,但你的运气之源在于只要没有析构函数,它就能够正常工作。更好了吗? - Steven Sudit
@Steven:不仅如此;new[]/delete[]允许使用与new/delete完全不同的实现(如果您替换为自己的实现,则很容易完成)。具有平凡析构函数的类型只是故事的一半。 - Roger Pate
@Roger Pate:你说得对。我想到的一个例子是,在为单个实例使用固定大小的分配时,仍然会为可变大小的块调用malloc。我试图表达的观点是,对于需要被销毁的对象,调用非数组删除很可能只会销毁第一个实例(如果它不仅仅是崩溃)。从这个角度来看,原始类型更不容易受到攻击。话虽如此,这并没有改变什么:我们永远不应该混合数组和非数组分配/删除。代码应该有意而为之,而不是靠运气。 - Steven Sudit

6

您必须记录该类的使用方式,并按预期分配。您还可以向对象传递标志,指定它应如何销毁。此外,请查看boost的智能指针,它可以为您处理此区别。


这基本上是正确的。如果你传递一个指针进来,要么A:只有一种“正确”的删除方式,要么B:另外一个“标志”变量表示哪种方式删除它,或者C:自定义删除器,这可能超出初学者的理解范围。在你的情况下,我建议选择“B”。 - Kevin Anderson
@Kevin:仅仅这些内容吗?我没错过什么重要的东西吧?我选择的选项并不是你提到的其中之一:尽快将指针转换为合适的智能指针,由智能指针来处理何时/如何/是否删除。使用auto_ptr或任何boost智能指针的基础并不难(尽管理解所有自定义和特别是实现细节是有难度的)。 - Roger Pate
由于该对象似乎具有所有权语义,他可能还想检查std::auto_ptr<>。 - Martin York
+1 这实际上只是另一种匹配正确的分配和释放例程的形式,这完全是用户的责任。就像free()必须与malloc()匹配(而free()本身无法检查),例如std::auto_ptr<X>必须与new匹配。- 我不会担心标志,为什么应该在运行时做出释放例程的决定,如果newnew[]的使用将在编译时完成呢? - UncleBens
@UncleBens:这不是关于在运行时做出决定,而是关于相同的代码/函数/类等具有多种用途。我想这似乎不明显,但boost::shared_ptr的自定义删除器是我打算与其他标志一起包括的一种类型。 - Roger Pate

5

简短回答:

如果您在new中使用[],则在delete中也要使用[]。

//allocate some memory
myObject* m = new myObject[100];

//later on...destructor...
delete m; //wrong
delete[] m; //correct

那就是基本框架,另一个你可以看看的是boost。考虑到你不确定它是数组还是单个对象,回答也相当困难。但你可以通过一个标志来检查,告诉你的应用程序是否使用delete或delete[]。

2
并没有真正回答他的问题。他很可能已经明白使用正确的“delete”函数很重要,否则他就不会问这个问题了。 :P - GManNickG
Boost 似乎可以解决所有问题...你有时候会有这样的想法吗? :) - JonH
3
不,如果 Boost 解决了“一切”,我就不会变秃了。但对于 C++ 编码来说,Boost 相当方便。 - Steven Sudit
@Steven Sudit - 真是太有趣了! - JonH

2
作为一般的开发规则,您应该坚持一种设计,即调用new的类也应该调用delete

4
这实际上与大多数当前的智能指针库完全相反:std::auto_ptr<int> ap(new int()); boost::shared_ptr<int> sp(new int()); 两者都会为您调用delete。 - Roger Pate

2

你根本不应该删除它。如果你的类使用一个已经初始化的指针,那么删除它是不安全的。它甚至可能不指向堆上的对象;调用deletedelete[]都可能导致灾难性后果。

内存的分配和释放应该在同一作用域内进行。无论哪段代码拥有并初始化了你的类实例,也应该负责初始化并传递指针,而那里才是你应该使用delete的地方。


@GMan 当你传递一个指针到一个类中时,如果这个类设计为接管它,那么显然会有例外情况... - user229044

2
  • 如果你使用new进行内存分配,使用delete释放。
  • 如果你使用new[]进行内存分配,使用delete[]释放。

如果在这些语句之后仍然存在问题(也许您想删除由其他人创建的对象),那么您正在违反第三条规则:

  • 始终删除您所创建的内容。推论是,永远不要删除您未创建的内容。

这第三条规则有时需要被打破,尽管我总体上同意它。 - Steven Sudit
第三条规则的推论是 - “永远不要创建智能指针” :-) - Tadeusz Kopec for Ukraine
实际上不是这样的。如果您创建一个对象,将其存储在智能指针中,并确保只有智能指针将被发布到外部世界,则您负责在同一位置创建和删除对象,这是很好的,即使实际删除不是由您自己完成的。 - Vincent Robert
我的意思是每个智能指针类都违反了“不要删除你没有创建的内容”的规则。而且问题似乎是关于类似于智能指针(拥有分配对象的所有权)的东西。在这种情况下,Michael Burr是正确的——要么说“我接受这种指针而不接受其他指针”——标准智能指针就是这样运作的,要么添加可通过指针传递的分配策略。 - Tadeusz Kopec for Ukraine

2

(按照要求将我的评论转化为答案。)

JonH的答案是正确的(关于只在使用数组构造时使用数组解构),因此也许您应该提供模板:一个用于数组,另一个不用。

另一个答案是避免使用数组,而是期望一个可能是适当的集合并在自身清理的单个实例,例如vector<>。

编辑

从Roger Pate大胆盗取,我将补充说,您可以要求使用智能指针,这相当于单个项目集合。


2
如果你有一个需要接管指针的类,那么使用该类的契约需要包含以下几点之一:
1. 接口需要说明指针所指向的对象是如何分配的,以便新所有者可以知道如何安全地释放该对象。这种方法的优点在于保持简单(至少在某个层面上),但不够灵活-类无法处理静态对象和动态分配的对象。
2. 接口需要包括一种机制,可以由提供指针的任何内容来指定释放策略。这可以是提供一种机制来传递函数对象(甚至是普通的函数指针),以便在同一函数/构造函数中调用它来释放对象。这使得该类的使用可能更加复杂(但是如果默认策略是对指针调用delete,则对于大多数用途而言,它与选项1同样易于使用)。现在,如果有人想给该类一个指向静态分配对象的指针,则可以传递一个空操作函数对象,这样当类想要释放它时,什么也不会发生;或者传递一个执行new[]分配的对象的delete[]操作的函数对象等。

+1 这是正确的答案。我很惊讶它没有被点赞。 - Tadeusz Kopec for Ukraine

1

由于C++中的指针无法告诉我们它是如何分配的,所以没有办法决定使用哪种释放方法。解决方案是将选择权交给用户,希望用户知道内存是如何分配的。请查看Boost smart ptr库,特别是shared_ptr构造函数的第二个参数,这是一个很好的例子。


给用户选择?这没有太多意义吧? - JonH
@JonH,针对容器的用户,即使用它的代码,而不是键盘上的用户 :) - Nikolai Fetissov
啊,我明白了,刚才有点糊涂。 - JonH

0
像boost shared_pointer这样的智能指针已经覆盖了这一点,你可以使用它吗?linky

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