为什么c++中的析构函数按与初始化顺序相反的顺序解除内存分配

Why the Destructor in C++ de-allocated memory in reverse order of how they were initialised?

本文关键字:顺序 分配 内存 初始化 c++ 析构函数 为什么      更新时间:2023-10-16

按变量的反向顺序解除内存分配的优势是什么?

考虑这个例子:

Type1 Object1;
Type2 Object2(Object1);

假设Object2使用了Object1的一些内部资源,只要Object1有效,CC_1就有效。例如,Object2的析构函数访问Object1的内部资源。如果不是为了保证反销毁顺序,这将会导致问题。

这不仅仅是关于释放内存,这是关于更广泛意义上的对称。

每次创建对象时,都在创建一个要在其中工作的新上下文。当你需要它们时,你"推"进这些上下文,然后"弹出"回来——对称性是必要的

当涉及到RAII和异常安全,或证明诸如先决条件和后置条件的正确性时,这是一种非常强大的思维方式(构造函数建立不变量,析构函数应该建立不变量,在设计良好的类中,每个方法都清楚地保留它们)。

恕我直言,缺少这个特性是Java最大的缺陷。考虑构造函数打开文件句柄或互锁的对象——Armen的回答很好地说明了这种对称性是如何强制执行一些常言性约束的(像Java这样的语言可能会让Object1在Object2之前离开作用域,但Object2通过引用计数使Object1保持存活),但是当考虑到对象生命周期时,有大量的设计问题可以很好地解决。

当你记住这一点时,很多c++的陷阱就会自己解释了

  • 为什么 gotos不能交叉初始化
  • 为什么你可能被建议在任何函数中只有一个return(这只适用于非raii语言,如C和Java)
  • 为什么异常是构造函数失败的唯一合理的方式,同样,为什么析构函数永远不能合理地抛出
  • 为什么不应该在构造函数中调用虚函数

等等…

局部变量的销毁顺序保证允许您编写(例如)这样的代码:

{
    LockSession s(lock);
    std::ofstream output("filename");
    // write stuff to output
}

LockSession是在构造函数中获取锁并在析构函数中释放锁的类。

},我们知道文件句柄将在锁被释放之前被关闭(并刷新),如果程序中有其他线程使用相同的锁来保护对同一文件的访问,这是一个非常有用的保证。

假设标准中没有指定销毁顺序,那么我们必须担心这段代码可能会释放锁(允许其他线程访问文件),然后才开始刷新和关闭它。或者,为了保持我们需要的保证,我们必须这样写代码:

{
    LockSession s(lock);
    {
        std::ofstream output("filename");
        // write stuff to output
    } // closes output
} // releases lock

这个例子并不完美——刷新文件并不能保证真正成功,所以依靠ofstream析构函数来完成它并不能在这方面产生防弹的代码。但是,即使存在这个问题,我们至少可以保证在释放锁之前不会再打开文件,通常这是销毁顺序可以提供的有用保证。

c++中还有其他的销毁顺序保证,例如基类的子对象在派生类的析构函数运行之后销毁,对象的数据成员在派生类的析构函数运行之后和基类的子对象之前按相反的构造顺序销毁。每一个保证都在那里,所以你可以在某种程度上写代码,依赖于第二个东西仍然存在,而第一个东西被销毁。

这些都与实际的内存回收没有太大关系,更多的是析构函数的作用。但是,由于您询问了有关取消分配的问题,在某些情况下,可能某些内存分配器实现受益于以与其分配相反的顺序释放块。它可以让分配器更容易地通过合并相邻的空闲块来减少内存碎片。不过,您不需要经常考虑这个问题,无论如何,需要合并空闲块的分配器应该足够聪明,无论它们被分配和释放的顺序如何,它都可以执行。