C++析构函数约定
C++ Destructor Conventions
我在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
指针是有效的,会发生什么?
像这样的小记账细节可能会产生非常难以调试的细微错误。在某些情况下,还不如迂腐一些,让编译器去掉不必要的东西。
- 什么时候调用组成单元对象的析构函数
- 如果C++类在类方法中具有动态分配,但没有构造函数/析构函数或任何非静态成员,那么它仍然是POD类型吗
- 内联映射初始化的动态atexit析构函数崩溃
- 什么时候调用析构函数
- 优先顺序:智能指针和类析构函数
- C++-明确何时以及如何调用析构函数
- 使用基类指针创建对象时,缺少派生类析构函数
- 在c++中使用向量时,如何调用构造函数和析构函数
- 重载运算符new[]的行为取决于析构函数
- 我需要知道编译器如何在cpp中使用析构函数
- 为什么在使用转换构造函数赋值后调用C++类的析构函数?
- 析构函数调用
- 通过引用传递-为什么要调用这个析构函数
- 对具有动态分配的内存和析构函数的类对象的引用
- 重载 -> shared_ptr 个实例中的箭头运算符<interface>,接口中没有纯虚拟析构函数
- C++成员的析构函数顺序与shared_ptr
- C++ 防止在映射中放置()时调用析构函数
- 在这种情况下显式调用时,std::cout 如何更改析构函数的行为?
- 为C++结构定义显式析构函数如何影响调用约定
- C++析构函数约定