"delete p; p = NULL(nullptr);"是一种反模式吗?



在SO上搜索时,我偶然发现了这个问题,并且对投票最多的答案的一个评论(对投票最多的答案的第五个评论)表明delete p; p = NULL;是一个反模式。我必须承认,我碰巧经常使用它,有时大多数时候使用if (NULL != p)检查。Man自己似乎建议这样做(请参阅destroy()函数示例),所以我真的很困惑,为什么认为反模式可能是如此可怕的事情。我使用它的原因如下:

  • 当我释放一个资源时,我也想让它失效以供进一步使用NULL是用来表示指针无效的正确工具
  • 我不想留下悬空指针
  • 我想避免双重/多重自由bug -删除一个NULL指针就像一个nop,但删除一个悬浮指针就像"搬起石头砸自己的脚"

请注意,我不是在"this"指针的上下文中问这个问题,让我们假设我们不是生活在一个完美的c++世界中,遗留代码确实存在并且必须维护,所以请不要建议任何类型的智能指针:)。

是的,我不建议这样做。

原因是额外设置为null只在非常有限的上下文中有帮助。如果在析构函数中,在析构函数执行后指针本身将不存在,这意味着它是否为空无关紧要。

如果指针被复制,设置p为空将不会设置其余的指针,然后你可以遇到同样的问题与额外的问题,你将期望找到被删除的指针为空,它将没有意义如何你的指针变成非零,但对象不存在....

此外,它可能隐藏其他错误,例如,如果您的应用程序试图删除指针多次,通过将指针设置为null,其效果是,第二次删除将被转换为无操作,而应用程序不会崩溃的错误逻辑仍然存在(考虑到以后的编辑访问指针之前的delete没有失败…怎么会失败呢?

我推荐这样做。

  • 显然,它在NULL是指针的有效值的上下文中是有效的。当然,这意味着如果在其他地方使用,必须进行检查。
  • 即使指针可能,技术上,不是NULL,它确实有助于在现实世界的情况下,当客户向您发送崩溃转储。如果它是NULL,而它不应该是NULL(并且它没有被你应该做的assert()测试所困),那么很容易看出这就是问题所在-你会在像mov eax, [edx+4]之类的地方崩溃,你会看到edx为0,你知道问题是什么。另一方面,如果指针没有变为NULL,但是指针被删除了,那么可能会发生各种各样的事情。它可能会工作,它可能会立即崩溃,它可能会稍后崩溃,它可能会显示奇怪的东西-在这一点上,任何发生的事情都是软不确定性的。防御性编程是王道。这包括在外部设置一个指向NULL的指针,即使您认为不必这样做,并且在一些地方进行额外的NULL检查,即使您在技术上知道它不应该发生。
  • 即使你有一个指针经过两次删除的逻辑错误,最好不要崩溃,并安全地处理它,而不是崩溃。这可能意味着你做了额外的检查,你可能想要记录,或者甚至优雅地结束程序,但这不仅仅是崩溃。顾客不喜欢这样。

我个人使用内联函数,它将指针作为引用并将其设置为NULL。

不,这不是一个反模式。

将指针设置为NULL是表明指针不再指向任何有效对象的完美方式。事实上,这正是NULL值所要表达的意思。

如果这里有要避免的反模式,那么反模式就是没有一组简单且定义良好的规则/约定来管理程序的内存。这些规则是什么并不重要,只要它们能够避免泄漏和崩溃,只要你能够并且确实始终遵循它们。

这取决于如何使用指针。如果所有可以看到指针的代码都应该"知道"它不再有效——特别是在指针即将超出作用域的析构函数中——就没有必要将其设置为null。

另一方面,指针可以表示一个对象,该对象有时存在,有时不存在,当该对象存在时,您可以使用if (p) { p->doStuff(); }之类的代码对其进行操作。在这种情况下,显然应该在删除对象后将其设置为null。

后一种情况的重要区别是,指针变量的生存期比它(有时)指向的对象的生存期要长得多,并且它的空值带有一些重要的含义,必须传达给程序的其他部分。

我认为反模式不是delete p; p = NULL;,而是assert(this != NULL)

我使用您所说的反模式有两个原因——首先是为了提高坏代码在没有隐藏的情况下崩溃的可能性,其次是为了在调试中使核心问题更明显。我不会把assert丢在我的代码里,只是因为它可能会捕捉到一些东西。

虽然我认为@Mark Ransom的思路是对的,但我认为还有一个比assert(this!=NULL)更根本的问题。

我认为你经常(直接)使用原始指针和new更能说明问题。虽然这本身(不一定)是代码异味/反模式/什么的,但它通常指向与C语言不必要的相似的代码,并且没有利用标准库中的容器之类的东西。

即使标准容器不能很好地满足您的需要,您仍然可以通常将指针包装到一个足够小、足够简单的包中,这样的技术就无关紧要了——您已经将对指针的访问限制在如此少量的代码中,您可以浏览它并确保它只在有效时使用。正如Hoare所说,做事有两种方式:一种是保持代码简单到明显没有缺陷;另一个是使代码如此复杂以至于没有明显的缺陷。只有当您已经在处理后一种情况时,这种技术才显得相关(对我来说)。

最终,我认为这样做的愿望基本上是承认失败——而不是试图确保代码是正确的,你已经做了相当于在沼泽旁边设置了一个bug灭虫器。它在很小的范围内减少了一小部分虫子的出现,但留下了更多的自由繁殖,如果对总人口有任何影响,那就太小了,无法衡量。

原因是额外设置为null只在非常有限的上下文中有帮助。如果在析构函数中,在析构函数执行后指针本身将不存在,这意味着它是否为空无关紧要。

必须纠正这个语句,因为它在c++中是假的。

被销毁对象的其他函数可以在对象被销毁时调用(因为销毁过程需要这样做)。它通常被认为是丑陋的,但并不总是有好的解决办法。

因此,清除指针可能是避免问题的唯一好的解决方案(即,这些被调用的其他函数可以在使用它之前测试该对象是否有效)

然而,c++中的一个好主意是使用智能对象(可能就是你所说的)。或多或少,一个类持有对对象的引用,并确保该对象在触发析构函数时被释放,并且不会在一个对象中同时添加许多对象来销毁(尽管结果是相同的,但这样更简洁)

相关内容

最新更新