在删除指针后,是不是很好的做法?

我会开始说, 使用智能指针,你永远不必担心这一点。

下面的代码有什么问题?

Foo * p = new Foo; // (use p) delete p; p = NULL; 

这是由另一个问题的答复和评论引发的。 尼尔·巴特沃斯 ( Neil Butterworth)的一则评论引起了一些注意:

在C ++中将delete指针设置为NULL并不是通用的良好实践。 有时候这是一件好事,有时候是毫无意义的,可以隐藏错误。

有很多情况下,它不会帮助。 但根据我的经验,这不能伤害。 有人开导我。

将指针设置为0(在标准C ++中为“null”,C中的NULL定义有所不同)避免了双重删除时的崩溃。

考虑以下:

 Foo* foo = 0; // Sets the pointer to 0 (C++ NULL) delete foo; // Won't do anything 

鉴于:

 Foo* foo = new Foo(); delete foo; // Deletes the object delete foo; // Undefined behavior 

换句话说,如果你没有把删除的指针设置为0,那么如果你正在执行双重删除,你将会遇到麻烦。 在删除之后将指针设置为0的一个参数就是这样做,只是掩盖了双重删除错误,并使它们不能被处理。

最好不要双重删除错误,但是根据所有权语义和对象生命周期,这在实践中很难实现。 我比UB更喜欢蒙面的双删除bug。

最后,关于pipe理对象分配的一个旁注,我build议你看看std::unique_ptr的严格/单一所有权, std::shared_ptr共享所有权,或另一个智能指针实现,根据您的需要。

在删除指向的内容后,将指针设置为NULL当然不会伤害到它,但是对于一个更基本的问题来说,它往往是一种创可贴:为什么你首先使用了一个指针呢? 我可以看到两个典型的原因:

  • 你只是想要在堆上分配一些东西。 在这种情况下,将其包装在RAII中将会更加安全和清洁。 当您不再需要该对象时,结束RAII对象的范围。 这就是std::vector工作方式,它解决了无意中将指针释放到释放内存的问题。 没有指针。
  • 或者你也许想要一些复杂的共享所有权语义。 从new返回的指针可能与调用delete的指针不同。 同时多个对象可能同时使用了这个对象。 在这种情况下,共享指针或类似的东西会更好。

我的经验法则是如果你在用户代码中留下指针,你就是在做错了。 指针不应该在那里指向垃圾。 为什么没有一个对象负责确保其有效性? 为什么它的范围没有结束,当指向对象呢?

我有一个更好的最佳实践:在可能的情况下,结束variables的范围!

 { Foo* pFoo = new Foo; // use pFoo delete pFoo; } 

在删除指向的对象之后,我总是将指针设置为NULL (现在是nullptr )。

  1. 它可以帮助捕获对释放内存的许多引用(假设您的平台在空指针的deref处出错)。

  2. 例如,如果你有指针的副本,它就不会捕获所有对free'd内存的引用。 但有些比没有好。

  3. 它会掩盖双重删除,但我发现这些比访问已经释放的内存要less得多。

  4. 在很多情况下,编译器将会优化它。 所以没必要的说法不能说服我。

  5. 如果你已经使用了RAII,那么在你的代码中并没有多lessdelete ,所以额外的分配导致混乱的说法并不能说服我。

  6. 在debugging的时候,看到空值而不是陈旧的指针通常很方便。

  7. 如果这仍然困扰你,请使用智能指针或参考。

当资源被释放时(通常只在封装资源的RAII封装器的析构函数中),我还将其他types的资源句柄设置为无资源值。

我在一个大的(900万报表)商业产品(主要在C)工作。 有一点,当内存被释放时,我们使用macros魔法来清空指针。 这立即暴露了很多潜伏的错误,并迅速修复。 据我记忆,我们从来没有一个双免费的bug。

更新:微软认为,这是一个很好的安全实践,并build议在他们的SDL政策的做法。 显然,如果使用/ SDL选项进行编译,MSVC ++ 11会自动(在许多情况下)自动踩下已删除的指针 。

首先,关于这个和密切相关的主题有很多现有的问题,例如为什么不删除将指针设置为NULL? 。

在你的代码中,这个问题正在进行(使用p)。 例如,如果你有这样的代码:

 Foo * p2 = p; 

那么将p设置为NULL就完成了很less,因为你仍然有指针p2担心。

这并不是说将一个指针设置为NULL总是毫无意义的。 例如,如果p是指向资源的成员variables,而资源的生命周期与包含p的类不完全相同,则将p设置为NULL可能是指示资源是否存在的有用方法。

如果delete后有更多的代码,是的。 当指针在构造函数或方法或函数的末尾被删除时,

这个比喻的要点是在运行时提醒程序员该对象已经被删除了。

更好的做法是使用自动删除目标对象的智能指针(共享或范围)。

正如其他人所说, delete ptr; ptr = 0; delete ptr; ptr = 0; 不会让恶魔飞出你的鼻子 但是,它鼓励使用ptr作为各种标志。 代码充斥着delete ,并将指针设置为NULL 。 下一步是分散if (arg == NULL) return; 通过您的代码来防止意外使用NULL指针。 一旦针对NULL的检查成为检查对象或程序状态的主要手段,就会出现问题。

我敢肯定,有一个关于使用指针作为标志的地方的代码味道,但我还没有find一个。

我会稍微改变你的问题:

你会使用未初始化的指针吗? 你知道,你没有设置为NULL或分配它指向的内存?

有两种情况下可以跳过将指针设置为NULL:

  • 指针variables立即超出范围
  • 您已经重载了指针的语义,并且不仅将其值用作内存指针,而且还将其用作键或原始值。 但是这种方法存在其他问题。

同时,争辩说将指针设置为NULL可能会隐藏错误,听起来像争辩说你不应该修复一个错误,因为修复可能会隐藏另一个错误。 如果指针未设置为NULL,则可能会显示的错误是尝试使用指针的错误。 但是将其设置为NULL实际上会导致与在释放内存中使用它相同的错误,不是吗?

如果你没有其他的约束条件,强制你在删除之后设置或者不设置指针为NULL(一个这样的约束被Neil Butterworth提到),那么我个人的偏好是保留它。

对我来说,问题不是“这是个好主意吗?” 但是“这样做会阻止或允许成功的行为是什么?” 例如,如果这允许其他代码看到指针不再可用,为什么其他代码甚至试图在释放后查看释放的指针? 通常,这是一个错误。

它也做了比必要的更多的工作以及阻碍事后debugging。 在你不需要的时候触摸内存越less,就越容易找出为什么有些东西崩溃了。 很多时候,我依靠内存与发生特定错误的情况类似的事实来诊断和修复所述错误。

删除后显式地置零强烈地向读者表明,指针表示概念上可选的东西。 如果我看到这样做,我会开始担心在指针被使用的地方,它应该首先对NULL进行testing。

如果这就是你的意思,最好使用boost :: optional之类的东西在源代码中进行显示

 optional<Foo*> p (new Foo); // (use p.get(), but must test p for truth first!...) delete p.get(); p = optional<Foo*>(); 

但是如果你真的希望人们知道指针已经“坏了”,我会与那些认为最好的事情是超出范围的人达成100%的协议。 然后,您使用编译器来防止在运行时出现错误的解除引用的可能性。

这是所有C ++洗澡水中的宝贝,不应该把它扔掉。 🙂

在一个结构良好的程序中进行适当的错误检查,没有理由把它赋值为null。 在这种情况下, 0单独是一个普遍认可的无效值。 很难失败,很快就会失败。

许多反对0的论点表明它可以隐藏一个bug或使控制stream程复杂化。 从根本上说,这或者是上游的错误(不是你的错(对不起的双关语))或者是程序员的另一个错误 – 甚至可能表明程序stream程已经变得太复杂了。

如果程序员想引入一个可能为空的指针作为特殊的值,并且写出所有必要的避免,那么这是他们故意引入的复杂性。 隔离效果越好,发现滥用的情况就越快,而且越能传播到其他项目中。

结构良好的程序可以使用C ++function来devise,以避免这些情况。 你可以使用引用,或者你可以说“传递/使用null或无效的参数是一个错误” – 一个同样适用于容器的方法,比如智能指针。 越来越一致和正确的行为禁止这些错误越来越远。

从那里,你只有一个非常有限的范围和上下文空指针可能存在(或被允许)。

同样可以应用于不是const指针。 遵循指针的值是微不足道的,因为它的范围非常小,不恰当的使用被检查和定义好。 如果您的工具集和工程师在快速阅读之后无法遵循该程序,或者存在不适当的错误检查或不一致/宽松的程序stream程,则还有其他更大的问题。

最后,当你想引入错误(涂鸦),检测对已释放的内存的访问,并捕获其他相关的UB时,你的编译器和环境可能有一些警卫。 您也可以在程序中引入类似的诊断程序,而不会影响现有的程序。

让我扩展你已经把你的问题。

下面是你以问题forms提出的问题:


在C ++中将delete指针设置为NULL并不是通用的良好实践。 有时候:

  • 这是一件好事
  • 以及时间毫无意义,可以隐藏错误。

然而,这是不好的时候 ! 你不会通过明确地置零来引入更多的错误,你不会泄漏内存,也不会导致未定义的行为发生。

所以,如果有疑问,就把它弄空。

话虽如此,如果您觉得您必须明确地将某些指针置为空,那么对于我来说,这听起来像是您没有足够的方法来分割,并且应该看一下名为“Extract method”的重构方法来将该方法拆分为分开的部分。

是。

它所能做的唯一的“伤害”就是将低效率(一种不必要的存储操作)引入到程序中 – 但是在大多数情况下,这种开销对于分配和释放内存块的成本来说是微不足道的。

如果你不这样做,你会有一些讨厌的指针,有一天抛弃错误。

我总是用一个macros来删除:

 #define SAFEDELETE(ptr) { delete(ptr); ptr = NULL; } 

(类似的数组,free(),释放句柄)

你也可以编写“自我删除”的方法来引用调用代码的指针,所以它们强制调用代码的指针为NULL。 例如,要删除多个对象的子树:

 static void TreeItem::DeleteSubtree(TreeItem *&rootObject) { if (rootObject == NULL) return; rootObject->UnlinkFromParent(); for (int i = 0; i < numChildren) DeleteSubtree(rootObject->child[i]); delete rootObject; rootObject = NULL; } 

编辑

是的,这些技术确实违反了关于使用macros的一些规则(是的,现在你可以用模板实现相同的结果) – 但是通过使用多年,我从来没有访问过死记忆 – 最难的和最困难的最耗时的去debugging你可能遇到的问题。 在实践中,多年来,他们已经有效地消除了我介绍过的每个团队中的一个空白类。

也有很多方法可以实现上述 – 我只是试图说明如果删除一个对象强制人们NULL指针的想法,而不是提供一种方法,让他们释放内存不会NULL调用者的指针。

当然,上面的例子只是朝向自动指针的一步。 我没有build议,因为OP是专门询问不使用自动指针的情况。

“有些时候,这是一件好事,时间是毫无意义的,可以隐藏错误”

我可以看到两个问题:简单的代码:

 delete myObj; myobj = 0 

在multithreading环境中成为一个线程:

 lock(myObjMutex); delete myObj; myobj = 0 unlock(myObjMutex); 

Don Neufeld的“最佳实践”并不总是适用。 例如,在一个汽车项目中,即使在析构函数中,我们也不得不指向0。 我可以想象,在安全苛刻的软件中,这样的规则并不less见。 遵循它们比试图说服团队/代码检查器在代码中使用每个指针更容易(也是明智的),使指针变为空的行是多余的。

另一个危险是在exception使用代码中依靠这种技术:

 try{ delete myObj; //exception in destructor myObj=0 } catch { //myObj=0; <- possibly resource-leak } if (myObj) // use myObj <--undefined behaviour 

在这样的代码中,您可能会产生资源泄漏并推迟问题或进程崩溃。

所以,这两个问题在我头脑中自然而然(Herb Sutter肯定会告诉我更多)为我提供了所有类似于“如何避免使用智能指针并用普通指针安全地完成工作”的问题。

总有悬念的指针担心。

如果要在重新使用指针之前重新分配指针(将其解除引用,将其传递给函数等),使指针为NULL只是一个额外的操作。 但是,如果您不确定在重新使用之前是否重新分配,将其设置为NULL是个好主意。

正如许多人所说,使用智能指针当然容易得多。

编辑:正如托马斯·马修斯在这个早期的答案中所说的 ,如果一个指针在析构函数中被删除了,那么就没有必要给它赋值NULL,因为它不会再被使用,因为对象已经被销毁了。

我可以想象,在删除一个指针后,如果在一个函数(或对象)中重复使用它的合理场景,在极less数情况下是有用的。 否则就没有任何意义 – 一个指针需要指向一个有意义的,只要它存在的时期。

如果代码不属于应用程序中性能最重要的部分,请保持简单并使用shared_ptr:

 shared_ptr<Foo> p(new Foo); //No more need to call delete 

它执行引用计数并且是线程安全的。 你可以在tr1(std :: tr1命名空间,#include <memory>)中find它,或者如果你的编译器没有提供它,就从boost中获取它。