zoukankan      html  css  js  c++  java
  • 单例模式简介以及C++版本的实现

        本篇博文主要内容参考 C++的单例模式一文,在此,为原作者耐心细致的分析讲解,表示感谢。本文将结合此篇文章,给出自己做实验后的理解以及代码,作为今天学习的小结。

        单例模式,它的意图是保证一个类仅拥有一个实例,并在对外提供一个全局访问点,该实例被所有模块共享。这种模式的应用范围很广,比如系统日志输出,操作系统的窗口管理器,PC连接的键盘等等。

        单例模式是一种设计模式,它的具体实现和各种语言特性有关,这里主要介绍在C++上面的实现,测试平台为Win7 64位,VS2010开发环境。

        根据参考博文中的例子,在此先列举一下各种实现策略,以下均以CSingleton为类名来举例。

        1. 使用一个全局对象,比如就叫CSingleton g_instance,优点是访问方便,缺点是不能保证此类对象唯一,除了全局对象外,还能够创建CSingleton的局部实例。

        2. 使用类的静态成员变量,此变量为私有的静态成员指针,如static CSingleton1 *m_pInstance;此时,需要考虑让类自己在合适的时候释放掉此成员指针所指向的内容。

        3. 使用类的静态成员变量,此变量为私有的静态成员,如static CSingleton1 m_pInstance;

     

    在给出代码前,要说明几个知识点:

         1. 类的静态成员(包括 成员变量和成员函数),属于类自身,所有实例对象均共享同一副本。

         2. 静态成员初始化操作在进入main函数之前,就已经分配空间并且完成初始化。静态成员变量必须在类体外初始化,通过类似 CSingleton1* CSingleton::m_pInstance = NULL的方式来定义初始化数值。如果不初始化,那么此成员就不会被分配空间,也就不会在类中存在

         4. 静态成员在程序退出main函数后,会转到CRT类函数的清理中,完成程序静态变量、类的析构函数等资源释放的调用,具体细节就无需考虑,只需要知道退出main函数还需要做清理工作就行。

    image

         3. 声明在类内部的类,成为嵌套类,它一般用来声明只在类内部使用的类。如果一定要在外部使用,需要加域解析符::

     

        好了,基本铺垫完成,开始码代码,先从简单开始。

        静态类变量方式,起先可以是类的成员变量,但需要在外部进行初始化,不优雅。其实,在类的静态成员函数中声明静态局部类变量,是一种更简洁的方法。

    class CSingleton  
    {  
    private:  
        CSingleton()   //构造函数是私有的  
        {  
        }  
        CSingleton(const CSingleton &);  
        CSingleton & operator = (const CSingleton &);  
    public:  
        static CSingleton & GetInstance()  
        {  
            static CSingleton instance;   //局部静态变量  
            return instance;  
        }  
    }; 

        静态类指针方式:这种方式实现,因为有分配空间,所以需要考虑的东西比较多。参考C++的单例模式一文中的代码,下面给出经过亲身实践后,无内存泄露的代码。重要的地方,都写上了注释,便于大家理解。

    // singlethon.cpp : 定义控制台应用程序的入口点。
    #include <iostream>
    #include <Windows.h>
    using namespace std;
     
    //用于开启CRT 内存泄露检测问题
    #define _CRTDBG_MAP_ALLOC 
    #include <stdlib.h> 
    #include <crtdbg.h> 
     
    //多线程保护锁类
    class Lock
    {
    private:
        CRITICAL_SECTION  m_cs; // 封装临界区
        Lock(){};
        Lock(const Lock&){};                                            
        Lock& operator=(const Lock&){};
     
    public:
        Lock(CRITICAL_SECTION cs):m_cs(cs)
        {
            InitializeCriticalSection(&m_cs);
        }
     
        void StartLock()
        {
            EnterCriticalSection(&m_cs);        
        }
     
        void StopLock()
        {
            // 离开临界区
            //LeaveCriticalSection(&m_cs);
        }
     
        ~Lock()
        {
            // 离开临界区 放在这里 貌似更好点
            LeaveCriticalSection(&m_cs);
     
            // 临界区不用的时候,进行销毁释放占用资源
            DeleteCriticalSection(&m_cs);
        }
    };
     
    //自己仿照参考,实现一个单例模式
    class Singlethon
    {
        //内嵌类,只能在Singlethon中使用,无法直接在外部使用
        class CGarbo    //唯一的作用,在析构函数中,删除Singlethon的实例
        {
        public:
            CGarbo()
            {
                cout << "constructor CGarbo"<<endl;
            }
            ~CGarbo()
            {
                if(Singlethon::m_pInstance)
                {
                    cout << "execute CGarbo destructor function"<<endl;
                    delete Singlethon::m_pInstance;
                }
            }
        };
     
    private:
        Singlethon(){cout << "constructor Singlethon "<<endl;};    //禁止直接声明 Singlethon single;
        Singlethon(const Singlethon&){};                        //禁止间接声明 Singlethon single2(*(Singlethon::GetInstance()));
                                                                //      或者   Singlethon single2 = (*(Singlethon::GetInstance()));
        Singlethon& operator=(const Singlethon&){};        //禁止赋值操作     Singlethon single;single = *(Singlethon::GetInstance());
            
        static Singlethon* m_pInstance;
        static CGarbo Garbo;        //静态成员变量,程序结束时,系统自动调用它的析构函数
        static CRITICAL_SECTION cs;
     
    public:
        static Singlethon* GetInstance();
     
        ~Singlethon()
        {
            cout << "execute Singlethon destructor function"<<endl;
            //不能在这里释放自身,因为CGarbox的析构函数会先于自身析构执行,而它在执行时,会调用 delete Singlethon::m_pInstance;
            //这会触发Singlethon自身的析构函数,而这里,再一次调用 delete Singlethon::m_pInstance,这就造成无限循环
            //最终会报错堆栈溢出的错误
            
        #if 0
            if(Singlethon::m_pInstance)            
                delete Singlethon::m_pInstance;
            Singlethon::m_pInstance = NULL;
        #endif 
        }
    };
     
    //以下三句话,缺一不可
    Singlethon::CGarbo Singlethon::Garbo;
    Singlethon* Singlethon::m_pInstance = NULL;
     
    Singlethon* Singlethon::GetInstance()
    {
        if (NULL == m_pInstance)
        {
            Lock lock(cs);
            lock.StartLock();
            if (NULL == m_pInstance)
            {
                m_pInstance = new Singlethon;
            }
            lock.StopLock();
        }    //正常退出或者异常抛出,这里都会自动调用lock的析构函数,因为lock的作用域是局部的
     
        return m_pInstance;
    }
     
    int main()
    {
        //开启CRT内存泄露检测功能
        _CrtSetDbgFlag ( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF ); 
     
        Singlethon* p1 = Singlethon::GetInstance();
        Singlethon* p2 = Singlethon::GetInstance();
        if ( p1 == p2)
        {
            cout << "单例模式测试成功!!"<<endl;
        }
        else
        {
            cout << "单例模式测试失败!!"<<endl;
        }
     
        return 0;
    }

    上述代码有几个地方要详细解析一下:

    1. 在释放类的时候,如果类中有动态变量成员,一般要先释放其中的内容,然后在调用析构函数释放类本身的空间。类似的,比如在vector<int*> array,如果直接array.clear,其中,分配的内存空间就会泄露,正确的方法是,先释放掉分配的内容,然后清空空间。

    for (vector<void *>::iterator it = v.begin(); it != v.end(); it ++) 
        if (NULL != *it) 
        {
            delete *it; 
            *it = NULL;
        }
    v.clear();

    2. 上述代码定义了一个锁类,封装了临界区的相关操作,作为资源管理类,内部有临界区变量,作为多线程安全的保证。局部资源管理类变量使得,当异常发生时,也能调用析构函数,释放临界区资源

    3. 在进行判断时,判断了两次,提高效率。因为该方法调用第一次就产生实例,而pInstance == NULL 大部分情况下都为false,如果只判断一次,那么每次获取实例前都需要加锁,效率太低。

    4. 定义一个嵌套类,和对应的静态局部变量,这样,当整个单例释放之前,就可以通过它来找到单例指针,然后delete掉他,这样就不会有内存泄露发送了。

    5. 使用CRT类函数的内存泄露检测功能,方便查看调试。

        文章的最后,给出测试结果图:

        image

        OK!

        读者可以在各个构造和析构函数中间加上端点,也可以尝试注释掉CGarbo的析构函数调试,看看有没有内存泄露。

        好久没有更新文章,这段时间工作很忙,忙不可怕,可怕的是没有目的、没用动机的忙。

     

        版权声明:本文为博主原创文章,欢迎转载,转载请注明出处,谢谢。

  • 相关阅读:
    js和java中使用join来进行数组元素的连接
    java的fail-fast 和 fail-safe机制
    chrome上表单的用户名密码autofill
    InnoDB索引底层是如何查询数据的?
    浏览器允许的并发请求资源数是什么意思?
    async/await的使用
    java获取HttpServletRequest 的客户端ip
    Free software
    Linux 配置 mysql 5.7.32 实操记录
    mysql 帮助手册翻译
  • 原文地址:https://www.cnblogs.com/cherishui/p/4818059.html
Copyright © 2011-2022 走看看