【问题标题】:A destructor - should I use delete or delete[]?析构函数 - 我应该使用 delete 还是 delete[]?
【发布时间】:2011-01-12 19:25:15
【问题描述】:

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

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

我对 C++ 有点陌生,所以请耐心等待。我一直使用 C,Java 是我选择的 OO 语言,但是在想要学习 C++ 和我的项目的速度要求之间,我选择了 C++。

将容器从模板更改为可以实现自己的析构函数的抽象类的容器会更好吗?

【问题讨论】:

  • JonH 的回答是正确的,所以也许您应该提供模板:一个用于数组,一个不用于。另一个答案是避免使用数组,而是期望单个实例可能是也可能不是在自身之后清理的正确集合,例如 vector.
  • @Steven Sudit:我认为你应该把这个评论作为答案。
  • 这种方法也可能存在线程问题 - 您无法删除在不同线程中分配的内容。
  • @Chris:通常你可以;它的跨进程会杀死你。但是单个进程在线程之间共享堆。这当然取决于实现。
  • 另外,@Alex:如果您是 C++ 新手,那么尝试找到可以理解的干净方法可能会很困难。通常更容易说“我正在打电话给delete,所以不要给我数组”,然后试着让它双向工作。理想情况下,人们无论如何都会使用vector 而不是原始内存数组。

标签: c++ templates destructor


【解决方案1】:

如果你不知道它是用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;

【讨论】:

  • 不是运气,是int没有析构函数。
  • @Steven:很幸运。 newdelete 不匹配会导致未定义的行为,句号。即使一个类型有一个微不足道的析构函数,也没有定义。对我来说,替换 operator newoperator delete 是完全合理的;我可以期望我的delete 函数中的指针已经由相应的new 输出。
  • @Steven - 很幸运,因为平台选择在类型没有析构函数时优化数组分配。但是另一个平台可以以不同的方式处理这个问题,然后该代码会在另一个平台上中断。
  • 我不想让它听起来像我在建议我们可以依靠简单类型总是有效,所以让我改写我的答案:这是运气,但是你幸运的来源是只要没有析构函数它就可以工作。更好?
  • @Steven:不止于此;允许 new[]/delete[] 使用与 new/delete 完全不同的实现(如果你用自己的替换很容易做到)。具有琐碎 dtor 的类型只是故事的一半。
【解决方案2】:

您必须记录该类的预期使用方式,并始终按预期分配。您还可以将标志传递给对象,指定它应该如何销毁。另请查看 boost 的 smart pointers,它可以为您处理这种区别。

【讨论】:

  • 这个基本正确。如果您要传递一个指针,则必须有 A:只有一种“正确”的删除方式,或 B:另一个“标志”变量,说明删除它的方式,或 C:自定义删除器,即无论如何,可能远远超出初学者的理解。在您的情况下使用“B”IMO。
  • @Kevin:基本上只是?我错过了什么吗?我选择的选项不是你提到的:尽快将该指针放入适当的智能指针,并让智能指针处理知道如何/是否/何时删除。 使用 auto_ptr 或任何 boost 智能指针的基础知识并不难(但要了解所有的自定义和尤其是实现细节)。
  • 由于对象似乎具有所有权语义,他可能还想检查 std::auto_ptr
  • +1 这实际上只是匹配正确分配和释放例程的另一种形式,这完全是用户的责任。正如free() 必须与malloc() 匹配(而free() 不能自己检查),例如std::auto_ptr<X> 必须与new 匹配。 - 不过,我不会为这个标志烦恼:如果newnew[] 的使用无论如何都将在编译时进行,为什么要在运行时完成释放例程的决定?
  • @UncleBens:这不是关于在运行时做出决定,而是关于相同的代码/函数/类/等。多才多艺。我想这并不明显,但 boost::shared_ptr 的自定义删除器是我打算与其他标志一起包含的一种标志。
【解决方案3】:

简答:

如果您将 [] 与 new 一起使用,您希望将 [] 与 delete 一起使用。

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

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

这是最基本的内容,您可以查看的另一件事是boost。考虑到您不确定它是数组还是单个对象,也很难回答。您可以通过一个标志来检查这一点,告诉您的应用是使用 delete 还是 delete[]。

【讨论】:

  • 并没有真正回答他的问题。他大概明白使用正确的delete 函数很重要,否则他不会问他的问题。 :P
  • boost 似乎可以解决所有问题..您有时有过这种想法吗? :)
  • 不,如果 Boost 解决了所有问题,我就不会秃顶了。但是对于 C++ 编码,它非常方便。
  • @Steven Sudit - 这很有趣!
【解决方案4】:

作为一般的开发规则,您应该坚持这样的设计:调用new 的类也应该调用delete

【讨论】:

  • 并非如此,这与大多数当前的智能指针库完全相反:std::auto_ptr<int> ap(new int()); boost::shared_ptr<int> sp(new int()); 都为您调用 delete。
【解决方案5】:

您根本不应该删除它。如果您的类采用已经初始化的指针,则删除它是不安全的。它甚至可能不指向堆上的对象;调用deletedelete[] 可能是灾难性的。

内存的分配和释放应该在同一个范围内进行。哪个代码拥有并初始化您的类的实例也可能负责初始化和传递指针,那个是您的delete应该在的地方。

【讨论】:

  • @GMan 当你传递一个指针的类被设计来获得它的所有权时,有明显的例外......
【解决方案6】:
  • 如果您使用new 分配,请使用delete
  • 如果您使用new[] 分配,请使用delete[]

在这些陈述之后,如果你仍然有问题(也许你想删除一个别人创建的对象),那么你就违反了第三条规则:

  • 始终删除您创建的内容。推论,永远不要删除你没有创建的东西。

【讨论】:

  • 第三条规则有时需要被打破,尽管我大体上同意它。
  • 第三条规则的推论是 - '从不创建智能指针' :-)
  • 其实没有。如果你创建一个对象,将它存储在一个智能指针中,并确保只有智能指针会发布到外部世界,那么你就可以在同一个地方负责创建和删除对象,这很好,即使实际删除不是自己做的。
  • 我的意思是每个智能指针类都违反了“永远不要删除你没有创建的东西”的规则。问题似乎是关于行为类似于智能指针的东西(获取分配对象的所有权)。在这种情况下,Michael Burr 是对的——要么说“我接受这种指针,而没有其他”——标准智能指针的行为方式就是这样,或者添加释放策略以与指针一起传递。
【解决方案7】:

(根据要求将我的评论转化为答案。)

JonH 的回答是正确的(关于仅在使用数组构造时才使用数组销毁),所以也许您应该提供模板:一个用于数组,一个不用于。

另一个答案是避免使用数组,而是期望单个实例可能是也可能不是在自身之后清理的适当集合,例如向量。

编辑

公然从 Roger Pate 那里窃取信息,我要补充一点,您可能需要使用智能指针,这相当于一个单项集合。

【讨论】:

    【解决方案8】:

    如果你有一个类,它需要一个指针,它将承担它的所有权,那么使用该类的合同需要包括几件事之一。要么:

    • 接口需要指示指针指向的对象是如何分配的,以便新所有者可以知道如何安全地解除分配对象。此选项的优点是使事情保持简单(无论如何都在一个层面上),但它不灵活 - 该类无法处理获取静态对象以及动态分配的对象的所有权。

    • 接口需要包含一种机制,在该机制中,释放策略可以通过任何提供指向类的指针来指定。这可以像提供一种机制来传递一个函数(甚至是一个普通的旧函数指针)一样简单,该函数将被调用以释放对象(最好在传递指针本身的同一函数/构造函数中)。这使得该类可以说使用起来更加复杂(但是,例如,在指针上调用delete 的默认策略可能使其在大多数情况下与选项 1 一样易于使用)。现在,如果有人想给类一个指向静态分配对象的指针,他们可以传入一个 no-op 仿函数,这样当类想要释放它时什么都不会发生,或者如果对象已分配,则将仿函数传递给 delete[] 操作new[]等。

    【讨论】:

    • +1 这是正确答案。我很惊讶它没有被投票。
    【解决方案9】:

    由于 C++ 中的指针没有告诉我们它是如何分配的,是的,没有办法决定使用什么释放方法。解决方案是让希望知道如何分配内存的用户可以选择。看看Boost smart ptr 库,尤其是shared_ptr 带有第二个参数的构造函数,这是一个很好的例子。

    【讨论】:

    • 把选择权交给用户?这没有多大意义?
    • @JonH,给 容器的用户,即使用它的代码,而不是 键盘上的用户 :)
    • 啊,我知道你了,一时糊涂了。
    【解决方案10】:

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

    【讨论】:

      【解决方案11】:

      简而言之,仅给定一个指向动态分配内存的指针,就无法确定如何安全地取消分配它。可以通过以下任何一种方式分配指针:

      • 使用新的
      • 使用新的 []
      • 使用 malloc
      • 使用用户定义的函数

      在所有情况下,在您释放内存之前,您必须知道它是如何分配的。

      【讨论】:

      • 你基本上可以决定你的类是代表一个 smart_ptr 还是一个 smart_array,然后让调用者为你的类提供一个适当分配的指针。
      • @UncleBens 不确定您的评论是什么意思 WRT 我的回答 - 尽管它的名字是“智能指针”不是指针。
      • 我的意思是,如果你只有一个指针,那么你就无法知道它是如何分配的。我不认为它就此结束。您简单地说,我这里有一个类将调用delete/delete[]/free()/用户定义的释放函数,由您的类的用户提供适当分配的指针。基本上你做了一些有意义的事情,剩下的不是你的问题。
      • @UncleBens 不要过多地批评你的风格,但在 cmets 中尝试区分回答者(默认的“你”)和提问者。
      • 好的。在我的 cmets 中,“你”通常是指一个类的设计者/程序员。正如我在您的回复中猜想的那样,您可能不是说:“......在你之前,亚历克斯,可以释放内存......”:)
      猜你喜欢
      • 2011-01-18
      • 2017-03-05
      • 1970-01-01
      • 2013-06-25
      • 2014-11-22
      • 2023-03-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多