• 单例模式的陷阱


    公司编辑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里面便少了这部分的内存泄漏记录。

  • 相关阅读:
    Google's Innovation Factory (and how testing adapts)
    虎年拜年帖
    [ZZ]让测试也敏捷起来
    Selenimu做爬虫续
    OKR的解说
    春秋航空的机上店铺
    免费TK域名试用
    快速排序_C语言实现
    第一篇博客
    C、C++代码格式优化软件献给编程爱好者
  • 原文地址:https://www.cnblogs.com/yao2yaoblog/p/10276015.html
Copyright © 2020-2023  润新知