为啥没有 C++ 的 DELETE 宏的原因

Posted

技术标签:

【中文标题】为啥没有 C++ 的 DELETE 宏的原因【英文标题】:Reason why not to have a DELETE macro for C++为什么没有 C++ 的 DELETE 宏的原因 【发布时间】:2010-11-18 22:28:24 【问题描述】:

是否有任何好的理由(也许“宏是邪恶的”除外)不使用以下宏?

#define DELETE( ptr ) \
if (ptr != NULL)      \
                     \
    delete ptr;       \
    ptr = NULL;       \


#define DELETE_TABLE( ptr ) \
if (ptr != NULL)            \
                           \
    delete[] ptr;           \
    ptr = NULL;             \

【问题讨论】:

宏并不是邪恶的,因为它们是邪恶的。它们不是命名空间的一部分,这使它们变得邪恶。 标准规定删除空指针是安全的,所以检查没有用。 我已经有一段时间没有使用 c++ 编程了,但我认为最好不要使用 Stroustrup 书中所述的 NULL。 在即将推出的 c++1x 标准中,他们最终将提供一个不能转换为数字的 null_ptr 构造 @Gavin Chin:相关:***.com/questions/704466/… 【参考方案1】:

我个人更喜欢以下的

template< class T > void SafeDelete( T*& pVal )

    delete pVal;
    pVal = NULL;


template< class T > void SafeDeleteArray( T*& pVal )

    delete[] pVal;
    pVal = NULL;

它们最终编译成完全相同的代码。

可能有一些奇怪的方法可以打破#define 系统,但就个人而言(这可能会让我感到痛苦;)我认为这不是什么大问题。

【讨论】:

确实,因为像 DELETE(ptr++) 这样的错误,作为宏更安全。 @Cătălin Pitiș:我会将其重命名为 DeleteAndNullify,以便在不阅读文档的情况下更清楚地了解其功能。而且因为“安全”并没有说明为什么它是安全的。 因为 RAW 指针应该被包装在一个类中以保护它们的破坏通常发生在析构函数中。此时对 NULL 的额外赋值就变得多余了。 @Zingam:如果我不传递对指针的引用,那么它将删除指针,但指针的值仍将与传入的值相同。通过传递对指针,那么我对指针所做的任何更改(即设置为nullptr)都不会发生。至于内联。除非(部分)专门化,否则模板会自动且必须内联,IIRC。 @Goz - 我并没有刻意试图变得晦涩,诚实。我刚刚用模板替换了我的代码库中的宏,它停止了编译。【参考方案2】:

因为它实际上并不能解决很多问题。

在实践中,大多数悬空指针访问问题都源于程序中其他地方存在另一个指向同一对象的指针,并且后来用于访问已删除的对象。

将未知数量的指针副本中的一个归零可能会有所帮助,但通常这是一个即将超出范围的指针,或者在任何情况下都设置为指向新对象。

从设计的角度来看,手动调用deletedelete[] 应该是比较少见的。按值使用对象而不是动态分配的对象,适当地使用std::vector而不是动态分配的数组,并将必须动态分配的对象的所有权包装在适当的智能指针中(例如auto_ptrscoped_ptrshared_ptr ) 来管理它们的生命周期是所有设计方法,使得用“更安全”的宏替换 deletedelete[] 是一种收益相对较低的方法。

【讨论】:

【参考方案3】:

因为可以删除NULL(0) 指针。不需要检查指针是否真的是NULL(0)。如果要在删除后将指针设置为 NULL,则可以在不使用宏的情况下全局重载delete 运算符。


第二点好像我错了:

如果要将指针设置为 NULL,删除后即可 重载delete 运算符 全球

问题是,如果你重载全局 newdelete,你可能会得到这样的结果:

void* operator new(size_t size)

    void* ptr = malloc(size);

    if(ptr != 0)
    
        return ptr;
    

    throw std::bad_alloc("Sorry, the allocation didn't go well!");


void operator delete(void* p)

    free(p);
    p = 0;

现在,如果您在重载的delete 中设置p = 0;,您实际上是在设置local,而不是原来的p。基本上,我们在重载的delete 中获得了指针的副本。

抱歉,这是在我的头上,我现在再考虑一下。无论如何,我会编写模板内联函数来做这件事,而不是编写 EVIL MACROS :)

【讨论】:

我有兴趣看看如何重载删除并将指针设置为空,您可以添加示例吗?当您重载“运算符删除”时,您会按值获取指针,因此将其设置为 null 不会修改原始调用中使用的指针。目前你有一个'+1',因为“不需要检查空指针”,但一个'-1'表示你可以重载 operator delete 并做同样的事情。 @Richard,不可能重载删除操作符以达到同样的效果。 @Richard Corden Man 我在想,我什至在编辑帖子后看到了你的评论。对不起,错误:) 我建议(重新)阅读 Effective C++ 的第 8 项。 new_handler 和零字节处理还有很多其他的魔法。我隐约记得在某处将所有形式的operator newoperator delete 作为最佳实践 进行覆盖。这实际上是我在 Effective C++ 中寻找的内容...【参考方案4】:

因为 DELETE 已经在 winnt.h 中定义了:

#define DELETE (0x00010000L)

【讨论】:

+1:现在有一个真正的理由不使用宏 - 命名空间污染。我想DELETE 也可以出现在其他地方。【参考方案5】: delete accept a NULL 指针没有问题,所以测试是多余的。 并非总是可以将指针重置为 NULL,因此无法系统地使用它们。 它们带来的安全性是虚幻的:根据我的经验,大多数悬空指针问题都来自于用于删除的指针以外的指针。

【讨论】:

你能解释一下你的说法“将指针重置为 NULL 并不总是可能的”吗?【参考方案6】:

您的宏失败有几个原因:

这是一个宏。它不遵守范围规则或许多其他语言特性,因此很容易被错误地使用。 可能导致编译错误:DELETE (getPtr()); 无法编译,因为您不能将函数调用设置为 null。或者如果指针是 const,你的宏也会失败。 它一无所获。 delete NULL 是标准允许的。

最后,正如 grimner 所说,您正在尝试解决一个本来就不应该存在的问题。你为什么要手动调用删除?`你不使用标准库容器吗?智能指针?堆栈分配? RAII?

正如 Stroustrup 之前所说,避免内存泄漏的唯一方法是避免调用 delete。

【讨论】:

【参考方案7】:

    删除空指针什么都不做,所以删除前不需要检查指针是否为空。可能仍需要取消已删除的指针(但并非在所有情况下)。

    应尽可能避免使用宏,因为它们难以调试、维护、引入可能的副作用、它们不属于命名空间等。

    删除不是用 new 动态分配的指针仍然是个问题...

【讨论】:

【参考方案8】:
    宏是邪恶的。为什么不使用内联 模板化函数? 您可以删除 空点。 在许多情况下,您不会 需要将 ptr 设置为 null - 例如析构函数。

【讨论】:

对于第 3 点:AFAICR,在某些情况下,即使在析构函数中不将 ptr 设置为 NULL,也会导致严重的错误。 当然只有在随后尝试使用该析构函数中的 ptr 时。无论哪种方式,您都将尝试取消引用 null ptr 或尝试将 ptr 用于已删除的对象,这两种行为都是未定义的。 公平地说,访问空指针通常比使用已删除对象“更少未定义”,因为在几乎所有情况下,空(或非常小的)指针访问都会导致硬件异常。不过,我确实同意您的观点 - 如果您正在清除已删除的指针,那么您的资源处理可能会遇到清除已删除指针无法解决的问题。 基本上 delete 不应该写在 dtors 之外的任何地方,这就是(与 moala 的声明相反)将指针设置为 NULL 的地方。即使在 dtors 之外,在编写良好的代码中,删除的指针通常会在 delete 之后超出范围。即使不是这样,将指针设置为NULL 实际上可能会掩盖指针被意外访问的错误。但最重要的是:为什么需要delete?我可以数出我在过去十年中用一只手的手指写过delete 的时间。事实上,我已经很多年没有写过它了。【参考方案9】:
    宏是邪恶的 :p 说真的,考虑使用inlined template functions 而是 在解除分配后设置指向NULL 的指针往往会掩盖错误 鼓励将if (ptr != NULL) 检查作为流控制机制。个人认为 这是void foo(int arg) 被替换为void foo(int arg, bool doAdvancedThings=false) 的代码气味 鼓励使用指向需要删除的内存的原始指针 - shared_ptr 和 其亲属应始终用于所有权,原始指针可用于 其他访问权限 鼓励在释放后查看指针变量,更糟糕的是使用 if (ptr != NULL) 而不是 if (ptr)... 比较指针是另一种代码味道

【讨论】:

"2. 释放后将指针设置为 NULL 往往会掩盖错误" 你能举个例子吗? @moala:如果您在指针指向的值被删除后访问指针,您的应用程序将崩溃。如果您将其设置为NULL,您的代码可能会检查并避免崩溃。尽管如此,您仍试图使用指向已删除对象的指针。 @D.Shawley:AFAIK,if (ptr != NULL) 实际上是 C 和 C++ 标准保证的唯一形式,尽管没有编译器供应商敢破坏 if (ptr) @sbi:FWIW,标准规定以下“空指针常量是整数类型的整数常量表达式右值,其计算结果为零”[conv.ptr]和“零值、空指针值或空成员指针值转换为false;其他值转换为true”[conv.bool]。我还认为if (ptr)if (ptr != NULL) 相当类似于if (flag)if (flag == true) 的布尔标志。我想这真的只是一种偏好。 @D.Shawley:看来我错了。奇怪的是,我似乎记得在过去十年中经常阅读此书。那么,也许这是一个神话。谢谢指正。【参考方案10】:

改用 boost::shared_ptr。

http://www.boost.org/doc/libs/1_39_0/libs/smart_ptr/shared_ptr.htm

此处的 MACRO 提供了您可能正在寻找的一些功能。

【讨论】:

【参考方案11】:

是的,你永远不应该直接调用 delete。使用 shared_ptr、scoped_ptr、unique_ptr 或您项目中的任何智能指针。

【讨论】:

似乎是一个非常严格的规则。它们也有局限性。 不,这不是一个特别严格的规则。这是完全避免内存泄漏的最简单方法,您在考虑什么限制? 不,不是来自标准,而是来自应用程序代码。指针操作只属于库内部。 原始指针不应该用于您拥有的东西——这就是智能指针的用途。如果您从不使用原始指针作为所有权,那么您也不会调用delete。它确实大大简化了您的代码。 如果您编写了一个 RIAA 容器,您认为需要在其析构函数中删除一个指针,那么您可能可以改用 scoped_ptr 或 scoped_array。这还具有不可复制的优点,它会阻止包含对象的默认副本。在编写 RIAA 类时,“使用 RIAA 进行资源处理”的规则仍然适用。 grimner 假设智能指针的可用性 - 很明显,他的建议的例外是在编写这些智能指针时,如果它们由于某种原因不可用。【参考方案12】:
    它不会给您带来太多好处。删除空指针是无害的,因此唯一的好处是在删除后将指针设置为 NULL。如果开发人员可以记住调用您的宏而不是删除,她也可以记住将指针清空,因此您并没有真正保护自己免受粗心的开发人员的伤害。唯一的好处是这发生在两行而不是一行。 这可能会造成混淆。删除是语言的标准部分。您的宏或模板函数不是。因此,新开发人员需要查看该宏定义以了解您的代码在做什么。

在我看来,收益不会超过成本。

【讨论】:

以上是关于为啥没有 C++ 的 DELETE 宏的原因的主要内容,如果未能解决你的问题,请参考以下文章

qt为啥raise没有效果

为啥Excel VBA中调用的exe不输出文件?

为啥 C++ 标准库中没有 SIMD 功能?

C++ 中的 new/delete 导致奇怪的内存泄漏

为啥 delete 不将指针设置为 NULL?

C++ 中的 delete vs delete[] 运算符