C++析构函数约定

C++ Destructor Conventions

本文关键字:约定 析构函数 C++      更新时间:2023-10-16

我在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;这三个语句是多余的,原因有两个:

  1. delete nullptr;是完全安全的,它什么也不做
  2. 如果对象被破坏并且内存被释放,那么将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指针是有效的,会发生什么?

像这样的小记账细节可能会产生非常难以调试的细微错误。在某些情况下,还不如迂腐一些,让编译器去掉不必要的东西。