使用重载的delete删除对象之前,进行NULL检查

25

这是其中一个代码审查评论中提到的问题。

在调用delete删除任何对象之前检查NULL是否是一个好主意吗?我知道delete操作符在内部检查NULL并且是多余的,但是提出的论点是,delete作为运算符可以被重载,如果重载版本不检查NULL,则可能会崩溃。那么现在和将来,假设delete被重载了,它是否安全和合理地假设重载的delete将会检查NULL呢?根据我的理解,合理地假设第一种情况是有重载delete来处理NULL检查,并且审查点无效。你怎么看?


7
哈哈,显然没有人(包括我在内)在回答之前完全阅读了问题。 - Konrad Rudolph
3
@Konrad - :) 告诉我们人们有多么热心愿意提供帮助。这是一个很棒的网站,我喜欢它! - Alok Save
请勿为C语言创建删除宏的原因。这种宏可能导致意想不到的行为,并且难以调试。相反,使用函数来处理此类操作,可以更安全地编写代码。 - moala
11个回答

35
不要检查null。标准规定delete (T*)0;是有效的。这只会使你的代码变得更加复杂,没有任何好处。如果operator delete被重载,最好在操作符实现中检查null。这可以节省代码行数和错误。
编辑:这个答案被接受并投票,但在我看来,它并不是非常有信息量。所有答案都缺少一个关键点,为了良心,让我在这里补充这个关键点。
标准实际上在[basic.stc.dynamic]中说到,至少从C++03开始:
任何在C++程序中定义的分配和/或释放函数,包括库中的默认版本,都必须符合3.7.4.1和3.7.4.2中指定的语义。
其中引用的部分以及其他答案中列出的标准中的一些其他地方都说,传递null指针的语义是无操作。

2
@Naveen @Als:是在弱意义上是的,在“符合”的强意义上是不行的。如果你实现的operator delete没有检查NULL,那么仍然是符合的(例如,它只是打印cout<<address<<'\n')。但如果它导致了未定义的行为,那么这就是你的operator delete实现中的一个错误。 - Yakov Galka
3
请仔细阅读ybungalobill的评论——如果不引发未定义行为,它就可以符合要求。例如:放置delete不对其参数执行任何操作(完全不进行任何处理),因此也不需要检查null。 - C. K. Young
2
@David:因此,使用自定义的new运算符时,您可以(并且应该)定义相应的自定义delete运算符,以便在对象构造失败时可以释放已分配的内存。放置delete对应于放置new(当然,它也什么都不做,只返回给定指针)。 - C. K. Young
3
@David: 对的,我并不是建议你自己使用placement delete,而是将其用作“必须检查null或不符合条件”的陈述的反例。 - C. K. Young
2
@David:正如@ybungalobill所述,检查NULL并不需要触发UB。因此,检查NULL并不需要符合规范,只需要它能够与NULL一起使用即可。 - Matthieu M.
显示剩余9条评论

19

我认为这更加需要确保如果你重载了operator delete,那么你应该始终检查NULL,否则你会破坏语义。


7

在调用delete删除任何对象之前,检查是否为NULL是一个好主意吗?

不是!

int *p = NULL;
delete p ; //no effect

标准规定 [第5.3.5/2节]

如果操作数具有类类型,则通过调用上述转换函数将操作数转换为指针类型,并在本节的其余部分中使用转换后的操作数代替原始操作数。 无论哪种选择,如果delete的操作数的值为null指针,则该操作无效

此外,在第 18.4.1.1/13 节中

void operator delete(void* ptr) throw();

void operator delete(void* ptr, const std::nothrow_t&) throw();

默认行为:

— 对于ptr的空值,不执行任何操作。

— ptr的任何其他值都必须是先前调用默认operator new返回的值,该值没有被介入调用operator delete(void*) (17.4.3.7)所使之失效。对于这样的非空值的ptr,回收先前调用默认operator new分配的存储空间。

编辑

James Kanze 在这里 说:

检查是否为空指针仍然是操作符 delete(或 delete[])的责任;标准并不保证它不会得到一个空指针;标准要求如果给予一个空指针,则它必须是无操作。或者实现允许调用它。根据最新草案,“提供给解分配函数的第一个参数的值可以是空指针值;如果是这样,并且解分配函数是由标准库提供的,则调用没有效果。”我不太确定“由标准库提供”这个条件的含义是什么——如果按照字面意思来理解,因为他的函数不是标准库提供的,那么这个句子似乎不适用。但不知何故,这没有意义。


2
@wilhelmtell:你看不到一个大大的NO吗? - Prasoon Saurav
我可以翻译,但是你的推理显然表明你在问题标题处停止阅读了。原帖作者知道这在默认情况下是一个noop。 - wilhelmtell
6
@wihelmtell:不,我没有在读完问题标题后停下来。这就是为什么我链接了bytes.com的帖子并要求他阅读James Kanze的答案。 - Prasoon Saurav

6
我认为过载的delete应该像你预期的那样行事,这是它的责任。也就是说,它应该将空指针处理为无操作。
因此,在调用重载的delete时,你不应该检查是否为空。你应该依赖于正确实现的重载delete

3

无需检查 null。delete 操作符会检查 null,因此不需要额外的检查。


3

delete (T*)0; 是有效的且不执行任何操作,同样地,free(NULL); 也是有效的且不执行任何操作。如果你重载了 delete 操作符,则你的实现应该具有相同的语义。标准规定了标准 delete 的工作方式,但我认为它没有说明重载的 delete 应该如何行为。为了与标准/默认行为保持一致,它应该允许输入 (T*)0


3

从标准文档中,delete 下的 18.5.1.1.13

默认行为:如果 ptr 为空,则不执行任何操作。 否则,回收先前调用 operator new 分配的存储空间。

因此,默认情况下无需检查。


2
不需要在删除之前检查是否为NULL。如果有人使用了与标准行为不同的重载delete,那么这才是真正的问题。没有人应该轻视重载delete的任务,并且应始终支持预期的行为,例如检查是否为NULL并采取不执行任何操作的措施。
然而,值得一提的是,您应该始终记得将刚删除的任何指针赋值为零,除非例如您即将删除该指针:
void MyObj::reset()
{
    delete impl_;
    impl_ = 0;    // Needed here - impl_ may be reused / referenced.
}

MyObj::~MyObj()
{
    delete impl_; // No need to assign here as impl_ is going out of scope.
}

2

不需要检查。如果有人过载该方法,他有责任对NULL进行任何操作。


1
我认为这个问题包含了不完整的信息。在我们的编码标准中,我的商店仍然有一个检查 NULL 的步骤,在删除之前进行检查,因为我们仍然必须支持一个编译器/平台配置,如果传递 NULL,则会进入未定义的行为。如果原始发布者有类似的情况,那么检查 NULL 是有意义的,否则,请更改编码标准!

抱歉,我的平台没有任何这样的限制,但是假设一个人总是编写可移植的代码,删除实现将不得不大多数情况下进行重载(基于内存泄漏检测的日志记录、特殊内存实现等),所以问题仍然成立,不是吗? - Alok Save
@Als 编译器/平台配置会对内置类型(即int、char等)产生未定义的行为,因为这实际上是平台free()实现中的一个错误,但由于该平台已经死亡,因此没有修复它的方法。 - diverscuba23

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