公司编辑UI有个编辑器,是个MFC框架的窗口。长时间以来,每次点关闭按钮关闭的时候都会报windows的调试,从而再启动一次。
平时大家工作都是赶任务,这个问题一直能忍则忍。后来这个问题导致我其他vs解决方案链接的时候老是失败。
由于这个编辑器会引用到另一个项目的lib,如果编辑器不能正常关闭,那么另一个项目链接的时候就会报无法打开某个lib。
于是我就想着怎么解决编辑器不能正常关闭的问题。
编辑器有源码,是MFC框架的。于是我首先简单找了一本侯捷的《深入浅出MFC》看了一下MFC的关闭流程。发现没有什么特别的地方。
为了保持环境干净,我重新clone了一份编辑器的代码,再重新生成。偶然发现每次关闭之后,工作目录下都会有个文件的修改时间变成新的。
难道有内存泄漏?而且我们的代码里有内存追踪模块?
打开文件看了之后,确认应该是有内存泄漏。而且每次几乎是一样的,泄漏的地方相同。这可能就好找了。
不过我好奇怎么做的内存追踪,于是我读了一下我们引擎里的内存追踪模块。这块另写一篇来讲。
这里讲一个单例模式的内存泄漏。还有怎么写能不内存泄漏。
直接看一下简化后的代码:
1 class Singleton 2 { 3 public: 4 static Singleton* Instance(); 5 Singleton() = default; 6 ~Singleton() = delete; 7 8 int mem = 1; 9 10 void printInstance() { cout << "print Instance" << endl; } 11 }; 12 13 Singleton * Singleton::Instance() 14 { 15 static Singleton* instance = new Singleton; 16 return instance; 17 }
1 int main() 2 { 3 Singleton::Instance()->printInstance(); 4 Singleton::Instance()->printInstance(); 5 6 return 0; 7 }
运行结果:
注意到几点:1.析构函数=delete。2.Instance()里有一个static的Singleton*,有new操作,但是没有对应的delete操作。
1.析构函数=delete。
这个c++11的新写法。代表该函数为删除函数,也就是不能重载,不会被调用。这类函数可以声明,但是没有定义。编译器会阻止对它们进行定义。
类似的如果想要阻止拷贝,阻止赋值拷贝,也可以把拷贝构造函数和赋值拷贝构造函数声明为删除函数。
Singleton(const Singleton&) = delete; Singleton& operator=(const Singleton&) = delete;
但是这里把析构函数声明为删除函数??这样就阻止了对象的析构了。
在《c++ primer》第五版的13.1.5节里有提到,析构函数不能是删除函数。
这里有什么其他的考量?不得而知。
2.static的Singleton*。
这里是一个已初始化局部的static变量,我们知道已初始话的static变量会放在.data区,而且会在程序结束的时候自动清理。
但是这里是new出来的对象,所以程序结束的时候是不会清理的。
怎么改比较好呢?
1.首先析构函数当然不能是删除函数。
2.new出来的static对象指针在程序结束不会自动清理,但是static对象会自动析构。可以利用这一点写一个static自动清理的类对象。
1 #include <iostream> 2 3 using namespace std; 4 5 class Singleton 6 { 7 public: 8 Singleton() { cout << "Singleton Constructor " << endl; } 9 ~Singleton() { cout << "Singleton Destructor " << endl; } 10 11 static Singleton* Instance(); 12 static Singleton* m_instance; 13 14 int mem = 1; 15 16 void printInstance() { cout << "print Instance" << endl; } 17 18 class AutoRelease 19 { 20 public: 21 AutoRelease() { cout << "AutoRelease Constructor " << endl; } 22 23 ~AutoRelease() 24 { 25 if (m_instance != nullptr) 26 { 27 delete(m_instance); 28 m_instance = nullptr; 29 cout << "AutoRelease Destructor" << endl; 30 } 31 } 32 }; 33 }; 34 35 Singleton* Singleton::m_instance = nullptr; 36 37 Singleton * Singleton::Instance() 38 { 39 if (m_instance == nullptr) 40 { 41 m_instance = new Singleton; 42 static AutoRelease auto_release; 43 } 44 return m_instance; 45 } 46 47 int main() 48 { 49 Singleton::Instance()->printInstance(); 50 Singleton::Instance()->printInstance(); 51 52 return 0; 53 }
运行结果:
如此就能正常析构了。
如此思路重构了代码,memeryleak里面便少了这部分的内存泄漏记录。