C++:我需要删除应用程序生命周期变量的指针吗?

11

我有一些“全局”构造,它们使用new分配,整个应用程序的生命周期都存在。

在应用程序完成之前是否需要打扰调用指针上的delete呢?毕竟,当应用程序关闭时,所有应用程序的内存不是都会被回收吗?

为了更清晰地说明,我仅讨论那些在程序关闭时“死亡”的生命期对象不需要调用delete的情况。


为什么不将全局变量从指针更改为智能指针呢?你可能仍然会遇到销毁顺序问题,但只有很少的情况。这有点取决于你最初设置事物的方式——如果(智能)指针最初为空,后来被分配,则通常比初始化分配对象的(智能)指针更难跟踪正确的构造/析构顺序。 - Steve Jessop
7个回答

25

从技术上讲,是的,内存被回收了。但是,除非您使用delete,否则这些对象的析构函数不会运行,它们的副作用也不会应用。这可能会导致临时文件未被删除或数据库更改未被提交,具体取决于那些析构函数的意图。

此外,还要记住墨菲定律。现在用于管理这些对象的代码是按照您描述的方式使用的(对象必须持续存在于程序的生命周期中),但以后您可能希望重用该代码,使其多次运行。除非它能正确地处理重新创建对象,否则它将泄漏对象。


1
这里的墨菲定律是双向的。如果您不删除,那么可以确定某些对象在其析构函数中具有重要行为。如果您删除,则可以确定该对象在某个静态对象的析构函数中使用,并且您将遇到销毁顺序问题。 - James Kanze
+1 for dtors. 当然,有些情况下你肯定不想调用dtors,例如当它们被困在库中并做出傻事时,比如永远等待阻塞线程终止。 - Martin James
这也对于内存工具(如valgrind)非常方便,使它们更有机会找到实际的泄漏。 - edA-qa mort-ora-y
也许在这里建议/提到使用RAII和智能指针,这将确保自动清理,而不是记得为每个原始指针显式调用“delete”。另一个避免在C++中使用原始指针的原因。 - Alok Save

2

无论如何,清理一切都是一个好的实践。尽管内存被释放了,这些对象可能会分配其他资源(共享内存、信号量等),应该通过对象的析构函数进行清理。

如果您不想调用delete,请使用共享指针来持有这些资源,以便在应用程序退出时正确清理它们。

您如何测试您的应用程序?不清理可能会妨碍开发一个体面的测试工具。应用程序的测试可能需要一种欺骗性关闭和重新启动的方法。

清理工作不仅仅是简单地释放内存。


当然有,因此最好避免尝试自己做,而选择一种经过验证的替代方案。 - Martin James

1

我认为你可能是对的,但我个人认为依赖系统并且不保证代码在关闭时始终整洁是糟糕的编码和不良实践。


你不信任你的操作系统吗?把时间浪费在已经被覆盖的工作上是徒劳无功的。此外,试图“干净地关闭”的应用程序通常容易出现根本没有关闭的情况——它们实际上比只是要求操作系统终止它们的应用程序更不可靠。 - Martin James
@Martin James:我理解你对于重复工作的看法,但是我认为你的论点实际上是为了懒惰或者走捷径找借口,这些习惯最终会导致问题。不,我绝对不相信操作系统,你不能假设操作系统会正确处理所有事情,在几乎每个平台上都有如此多的操作系统/更新等组合,无论是现有的还是未来的组合,你的代码可能会遇到你绝对不能假设一切都会按照你的意图工作。唯一确定的方法就是确保你自己处理好每一个细节。 - Richard Baxter
我的操作系统每天都会被数十亿用户进行大量的浸泡测试。我认为我写的代码非常不错,但是我无法承担同样的测试成本,因此我尽可能地利用操作系统。 - Martin James

1

没有一个正确的答案。大多数情况下,这可能并不重要,但是有些析构函数会执行一些重要的操作,而不仅仅是释放内存(我有一个析构函数用于删除临时文件),这支持清理的论点;另一方面,如果这些对象被其他对象的析构函数使用,则析构此类对象可能会导致销毁顺序问题。我的一般规则是不进行析构,除非析构函数执行的操作不仅仅是释放内存,但是其他人可能更喜欢不同的默认设置。


1
不要去编写/调试/维护已经非常擅长处理的操作系统的代码。
除非有特殊原因(例如,需要提交未完成的事务、刷新文件、关闭连接),否则我不会费心去编写操作系统本来就会处理的代码。如果析构函数没有特别的功能,为什么还要调用它呢?
许多开发人员在应用程序关闭时花费大量精力来删除/销毁/释放/终止一些东西,只是为了避免内存管理器在应用程序关闭时生成一份虚假的“泄漏报告”,而这个内存管理器本身即将被销毁。

7
太神奇了,为什么得票数为0的答案被选中,而得票数为9的[正确]答案却未被选中?马丁,你的方法存在问题,因为它需要太多的知识。也就是说,你必须针对每个特定情况知道析构函数是否“做了特殊处理”。养成始终编写正确代码的习惯要好得多,这样无论析构函数如何编写都不会有影响。如果你调用的是库代码,甚至不知道析构函数会做什么,这一点尤其重要。 - Cody Gray
4
总体教训是:重点不在于让代码容易编写,而是要让其易于维护 - Cody Gray
2
如果你不整理以删除“虚假”的泄漏报告,那么你怎么知道哪些泄漏报告不是“虚假”的呢? - Mike Seymour
@CodyGray - 可能是因为得到9个(现在是11个)赞同的答案并不与我的答案相矛盾,而是对其进行了补充。 - Martin James
@CodyGray - 我同意维护问题 - 我曾经对其他开发人员的代码进行了大量的代码维护 :( 对于一个复杂应用程序的关闭序列来说,这是非常棘手的。它需要设计、实现、测试和维护。如果能够将其最小化,甚至完全避免,那么该应用程序将更加可靠,并且需要更少的维护。 - Martin James
显示剩余12条评论

0
除了析构函数不被执行(如 sharptooth pointer out 所提到的),删除全局对象也很重要,以便使内存检查器保持快乐。特别是如果你的代码是在共享库中 - 你不想因为没有正确删除而使它们的内存检查器(比如 Valgrind)输出混乱。

不利之处是需要编写、测试和维护一个关闭机制。优势是在valgrind中没有杂泄漏日志。 - Martin James
@MartinJames:编码、测试和维护很可能不是缺点,而只是必需品:想象一下一个共享库,在全局范围内使用new创建对象,但却没有使用delete释放。如果这个库被重复加载/卸载(使用dlopen/LoadLibrary等),你实际上会不断地泄漏更多的资源。你必须通过编写代码、测试和维护来处理这个问题。 - Frerich Raabe
我发表了“没有虚假泄漏”的言论。如果一个库像你描述的那样泄漏,那么当然要解决问题!我唯一关心的是修复不存在的问题。 - Martin James

0

然后还有那些情况,你绝对不希望在操作系统终止进程之前调用dtor,例如:

1)当dtor无法正常工作时,因为它试图终止一个线程,失败并阻塞在线程句柄或其他信号上(永恒的“join/waitFor”死锁),这是所有家庭“我的应用程序无法干净地关闭”的帖子的99%的原因。

2)当dtor无法正常工作时,因为它本身就很糟糕,并且被埋在库中。

3)当内存必须比进程线程更长寿时,否则在关闭时会出现segfaults/AV(例如,缓冲对象池,线程可能会在关闭时写入)。

4)任何其他需要将对象的销毁留给操作系统的“特殊情况”。

有这么多“特殊情况”,我认为“清理”关闭代码是特例。


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