为什么 C++ 中的析构函数?以与初始化相反的顺序取消分配内存?
以与变量相反的顺序取消分配内存有什么好处?
What is the advantage in de-allocating memory in reverse order to variables?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
以与变量相反的顺序取消分配内存有什么好处?
What is the advantage in de-allocating memory in reverse order to variables?
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
接受
或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
发布评论
评论(3)
考虑以下示例:
假设
Object2
使用Object1
的某些内部资源,并且只要Object1
有效,它就有效。例如,Object2
的析构函数访问Object1
的内部资源。如果不是有逆序销毁的保证,这就会出问题。Consider this example:
Suppose that
Object2
uses some internal resources ofObject1
and is valid as long asObject1
is valid. For example,Object2
s destructor accessesObject1
's internal resource. If it weren't for the guarantee of reverse order of destruction, this would lead to problems.这不仅仅是关于内存的释放,更是关于更广泛意义上的对称。
每次创建对象时,您都会创建一个新的工作上下文。您可以根据需要“推入”这些上下文,然后再“弹出”回来 - 对称性是必要的。
当涉及到 RAII 和异常安全,或者证明前置条件和后置条件的正确性时,这是一种非常强大的思维方式(构造函数建立不变量,析构函数应该
assert()
它们,并且在精心设计的类中每种方法都清楚地保留了它们)。恕我直言,缺乏这个功能是 Java 最大的缺陷。考虑构造函数打开文件句柄或互斥体或其他任何东西的对象 - Armen 的答案出色地说明了这种对称性如何强制执行一些常识性约束(Java 等语言可能会让 Object1 在 Object2 之前超出范围,但 Object2 通过引用计数使 Object1 保持活动状态),但是从对象生命周期的角度考虑时,有大量的设计问题可以很好地解决。
当您牢记这一点时,许多 C++ 陷阱就会自行解释
goto
不能交叉初始化return
(这仅适用于非 RAII 语言,例如 C 和 Java)等 中调用虚函数 ETC...
It's not just about deallocating memory, it's about symmetry in a broader sense.
Each time you create an object you are creating a new context to work in. You "push" into these contexts as you need them and "pop" back again later -- symmetry is necessary.
It's a very powerful way of thinking when it comes to RAII and exception-safety, or proving correctness w.r.t. preconditions and postconditions (constructors establish invariants, destructors ought to
assert()
them, and in well-designed classes each method clearly preserves them).IMHO, lack of this feature is Java's single biggest flaw. Consider objects whose constructors open file handles or mutexes or whatever -- Armen's answer brilliantly illustrates how this symmetry enforces some common-sense constraints (languages such as Java may let Object1 go out of scope before Object2 but Object2 keeps Object1 alive by reference counting) but there's a whole wealth of design issues that fall neatly into place when considered in terms of object lifetimes.
Lots of C++ gotchas explain themselves when you bear this in mind
goto
s can't cross initialisationsreturn
in any function (this only applies to non-RAII languages such as C and Java)etc etc...
局部变量的销毁顺序的保证是允许您编写(例如)如下代码:
LockSession
是一个在其构造函数中获取锁并在其析构函数中释放它的类。在
}
处,我们知道文件句柄将在释放锁之前关闭(并刷新),如果还有其他线程,这是一个非常有用的保证在使用相同锁来保护对同一文件的访问的程序中。假设标准没有指定销毁顺序,那么我们就必须担心此代码可能会释放锁(允许其他线程访问该文件),然后才开始刷新并关闭它。或者为了保持我们需要的保证,我们必须编写这样的代码:
这个示例并不完美 - 刷新文件并不能保证真正成功,因此依赖
ofstream
析构函数来执行此操作不会导致在这方面产生防弹代码。但即使存在这个问题,我们至少可以保证在释放锁时我们不会再打开文件,一般来说,这是销毁顺序可以提供的那种有用的保证。C++ 中还有其他销毁顺序的保证,例如,基类子对象在派生类析构函数运行后被销毁,对象的数据成员也在派生类析构函数运行后以与构造相反的顺序销毁并在基类子对象之前。每个保证都存在,以便您可以编写以某种方式依赖于第二个东西仍然存在而第一个东西被破坏的代码。
这些都与内存的实际解除分配没有太大关系,更多的是关于析构函数的作用。不过,既然您询问了解除分配的问题,那么在某些情况下,某些内存分配器实现可能会受益于以与分配相反的顺序释放块。它可以使分配器通过合并相邻的空闲块更容易地减少内存碎片。不过,您并不经常需要考虑这一点,无论如何,需要合并空闲块的分配器应该足够聪明,无论分配和释放的顺序如何,都应该足够聪明。
The guarantee of destruction order of local variables is to allow you to write (for example) code like this:
LockSession
is a class that acquires the lock in its constructor and releases it in its destructor.At the
}
, we know that the file handle will be closed (and flushed) before the lock is released, which is a very useful guarantee to have if there are other threads in the program that use the same lock to protect access of the same file.Suppose that destruction order were not specified by the standard, then we'd have to worry about the possibility that this code would release the lock (allowing other threads to access the file), and only then set about flushing and closing it. Or to keep the guarantee we need, we'd have to write the code like this, instead:
This example isn't perfect - flushing a file isn't guaranteed to actually succeed, so relying on an
ofstream
destructor to do it doesn't result in bullet-proof code in that respect. But even with that problem, we are at least guaranteed that we don't have the file open any more by the time we release the lock, and in general that's the sort of useful guarantee that destruction order can provide.There are also other guarantees of destruction order in C++, for example that base class subobjects are destroyed after the derived class destructor has run, and that data members of an object are destroyed in reverse order of construction, also after the derived class destructor is run and before the base class subobjects. Each guarantee is there so that you can write code that relies in some way on the second thing still being there while the first thing is destroyed.
None of this has very much to do with the actual de-allocation of memory, it's much more about what the destructor does. Since you ask about de-allocation, though, there might be some cases where certain memory allocator implementations benefit from blocks being freed in reverse order of their allocation. It could make it a little bit easier for the allocator to reduce memory fragmentation by merging adjacent free blocks. You don't very often have to think about that, though, and anyway allocators that need to merge free blocks ought to be smart enough to do it whatever order they're allocated and freed.