我在C++中看到了很多类似下面例子的代码,这通常被推荐为一种惯例:
class Foo
{
public:
Foo()
{
bar = new int[100];
size = 100;
}
// ... copy/assignment stuff ...
~Foo()
{
if (bar) // <--- needed?
{
delete[] bar;
bar = nullptr; // <--- needed?
}
size = 0; // <--- needed?
}
private:
int* bar;
int size;
};
对我来说,if (bar) { ... }
、bar = nullptr;
和size = 0;
这三个语句是多余的,原因有两个:
delete nullptr;
是完全安全的,它什么也不做- 如果对象被破坏并且内存被释放,那么将
bar
设置为nullptr
并将size
设置为0就不必担心安全性了
这些理由正确吗?这些说法真的是多余的吗?如果是这样,为什么人们一直在使用和建议它们?我希望看到一些可以通过保留这一公约来解决的潜在问题。
你说得对,这些都不需要,有些编译器无论如何都会对它们进行优化。
但是-人们通常这样做的原因是为了帮助发现问题。例如,假设您没有将指针设置为null。对象被破坏,但随后您错误地尝试访问(曾经的)指针。由于运行时可能不会清除它,您仍然会在那里看到一些有效的内容。这只有在调试中才有价值,而且它仍然是未定义的行为,但有时会得到回报。
这个usually recommended as a convention
在哪里?
通常建议不要手动管理内存分配。如果您使用std::vector
(或者如果您真的想使用智能指针)来实现这一点,那么所有的问题都会完全消失,您根本不需要编写析构函数。
然而,如果你真的坚持这样做(为了正确起见,你已经编写了复制构造函数/复制赋值,但没有向我们展示),那么我发现析构函数中的额外工作实际上隐藏了真正发生的事情,并且以代码模糊为代价提供了很少的价值。
您应该使用delete[]
(数组)
if
确实没用。性能方面的影响通常可以忽略不计。当你调试时,你有时会很高兴事情处于安全状态(比如防止看到一些释放的内存并挠头)。当它隐藏在树的深处时,它会有所帮助(至少可以重新初始化大小和条)。
顺便说一句,更好的写作方式是(RAII:http://en.wikipedia.org/wiki/Resource_Acquisition_Is_Initialization)
Foo():
bar (new int[100]),
size(100)
{
}
编辑:
- 旧习惯很难改掉,为此,你应该使用
scoped_ptr
并对此感到满意(或者更好的是,使用vector
),并杀死普通的愚蠢指针 - 老家伙写了太多的C或在太多的语言之间切换:便宜的时候安全总比抱歉好
正如许多其他答案中提到的,if(bar)
相当愚蠢。
不过,将释放的指针设置为nullptr
并重置size = 0
也有其用途。当您拥有虚拟构造函数、虚拟析构函数和类层次结构时,OOP中的一些冗余是很好的。在析构函数过程中,如果子类释放指针但没有将其设置为nullptr
,然后基类也试图释放它,会发生什么?在类似的情况下,如果基类假设如果size > 0
指针是有效的,会发生什么?
像这样的小记账细节可能会产生非常难以调试的细微错误。在某些情况下,还不如迂腐一些,让编译器去掉不必要的东西。