我们继承了大型遗留应用程序,其结构大致如下:
class Application
{
Foo* m_foo;
Bar* m_bar;
Baz* m_baz;
public:
Foo* getFoo() { return m_foo; }
Bar* getBar() { return m_bar; }
Baz* getBaz() { return m_baz; }
void Init()
{
m_foo = new Foo();
m_bar = new Bar();
m_baz = new Baz();
// all of them are singletons, which can call each other
// whenever they please
// may have internal threads, open files, acquire
// network resources, etc.
SomeManager.Init(this);
SomeOtherManager.Init(this);
AnotherManager.Init(this);
SomeManagerWrapper.Init(this);
ManagerWrapperHelper.Init(this);
}
void Work()
{
SomeManagerWrapperHelperWhateverController.Start();
// it will never finish
}
// no destructor, no cleanup
};
所有管理人员一旦创建,就会在整个应用程序生命周期内停留应用程序没有关闭或关闭方法,管理器也没有。因此,从不处理复杂的相互依赖关系。
问题是:如果对象的生命周期与应用程序生命周期紧密结合,那么根本不接受清理的做法是否被接受?操作系统(在我们的例子中是Windows)是否能够在进程结束后清除所有内容(终止线程,关闭打开的文件句柄,套接字等)(通过在任务管理器中结束它或通过调用ExitProcess,Abort等特殊函数)等等。)?上述方法可能存在哪些问题?
或者更通用的问题:对于全局对象(在main之外声明)是绝对必需的析构函数吗?
答案 0 :(得分:3)
完全没有清理
是否是公认的做法
这取决于你问的是谁。
操作系统(在我们的例子中是Windows)是否能够清理 一切(杀死线程,关闭打开文件句柄,套接字等)一次 过程结束
是的,操作系统将收回所有内容。它将要求记忆,免费处理等。
上述方法可能存在哪些问题
可能存在的问题之一是,如果您使用内存泄漏检测器,它将不断显示您有泄漏。
答案 1 :(得分:3)
操作系统(在我们的例子中是Windows)是否能够清理 一切(杀死线程,关闭打开文件句柄,套接字等)一次 该过程结束(通过在任务管理器中结束或通过调用特殊 函数如ExitProcess,Abort等)?有什么可能的问题 用上面的方法?
只要您的对象没有初始化操作系统清理的任何资源而不是,那么无论您是否明确清理它都没有任何实际区别,因为操作系统将当您的流程终止时,请为您扫描。
但是,如果您的对象 正在创建未被操作系统清理的资源,那么您就遇到了问题,需要在应用程序的某个地方使用析构函数或其他一些明确的清理代码。
考虑其中一个对象是否在某些远程服务上创建会话,例如数据库。当然,操作系统并不神奇地知道这已经完成或者当你的进程死亡时如何清理它们,所以这些会话将保持打开直到某些东西杀死它们(DBMS本身可能通过执行某些超时阈值或其他) 。如果您的应用程序是资源的小用户并且您在大型基础架构上运行,也许不是问题 - 但如果您的应用程序创建并且然后孤立足够的会话,则该远程服务上的资源争用可能开始成为问题。
如果对象的生命周期与应用程序紧密耦合 一生中,根本不接受清理是不可接受的做法?
这是一个主观辩论的问题。我个人的偏好是包括明确的清理代码,并使我创建的每个对象在任何可行的地方负责清理。如果应用程序生命周期对象被重构,使得它们不再存在于对象的生命周期中,我不必回过头来弄清楚是否需要添加先前省略的清理。我想清理我说我通常更倾向于RAII而不是更务实的YAGNI。
答案 2 :(得分:1)
通常,现代操作系统会在退出时清理所有进程资源。但在我看来,自己清理后仍然是很好的举止。 (但后来我在Amiga上“被提升”了,你已经去做了。)
答案 3 :(得分:1)
有时它会被一个规范或者只是“外围设备”的行为强加给你。也许你的应用程序中有很多缓冲的数据应该真正刷新到磁盘,或者数据库可能积累的“半开”连接没有明确关闭。
除此之外,正如@cnicutar所说,这取决于你的要求。由于以下原因,我坚定地参加了“不要打扰”阵营:
1)如果不编写不需要的额外关闭代码,那么让应用程序无论如何都很难工作。
2)你编写的代码越多,错误就越多,你需要做的测试就越多。您可能必须在多个操作系统版本中测试此类代码:(
3)操作系统开发人员花了很长时间确保应用程序可以随时关闭(例如,通过Task Manger),而不会对系统的其他部分产生任何影响。如果操作系统中已有某些功能,为什么不利用它呢?4)线程造成了一个特殊问题 - 它们可能处于任何状态。它们可能运行在与启动应用程序关闭的线程不同的核心上,或者可能在系统调用时被阻止。虽然操作系统很容易确保在释放任何内存,关闭句柄等之前终止所有线程,但是很难以安全可靠的方式从用户代码中停止这些线程。
5)性能损失的内存管理器不是检测泄漏的唯一方法。如果汇集了大型对象(例如网络缓冲区),则可以很容易地判断运行期间是否存在任何泄漏,而不依赖于在应用程序关闭时发出泄漏报告的第三方内存管理器。像Valgrind这样密集的内存检查器实际上会影响整体时间,从而导致系统问题。
6)根据经验,当用户点击“红十字”边框图标时,我为Windows编写的没有明确关闭代码的每个应用程序都会立即完全关闭。这使得繁忙,复杂的IOCP服务器运行在具有数千个连接客户端的多核盒上。
7)假设已经完成了一个合理的测试阶段 - 包括加载/浸泡测试 - 确定泄漏的应用程序与选择不在接近时使用的可用内存的应用程序进行区分并不困难。漏勺应用程序将显示内存/句柄/随着运行时间不断增加。
8)不明显的小的,偶然的泄漏不值得花费大量的时间。大多数Windows机箱每个月都会重新启动,(补丁星期二)。
9)不透明的库通常由像我这样的开发人员编写,因此无论如何都会在关机时生成虚假的“泄漏报告”。
设计/编写/调试/测试关闭代码仅仅是为了清理内存报告,这是一件昂贵的奢侈品,我可以不用这样做:)
答案 4 :(得分:0)
您应该分别为每个对象确定。如果一个对象需要在清理时采取特殊操作(例如将缓冲区刷新到磁盘),除非您明确处理它,否则不会发生这种情况。