C++设计模式5–单例模式Singleton

作者: veaxen 分类: 设计模式 发布时间: 2019-02-22 21:50

引言

很多情况下,我们在开发项目的过程中,都希望自己运行的某个部件只有一个实例,比如我们天天用QT开发界面,QTCreate里帮助菜单下的关于Qt Create菜单,弹出来的关于对话框,在QTCreate运行过程中,不论单击多少次,弹出的总是同一个对话框,这里的关于对话框就是一个单例模式实现的对象。

再比如说我们经常用的Windows下的任务管理器,无论打开多少次,都是同一个任务管理器对话框。

单例模式概述

单例模式是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例类的特殊类。通过单例模式可以保证系统中一个类只有一个实例而且该实例易于外界访问,从而方便对实例个数的控制并节约系统资源。如果希望在系统中某个类的对象只能存在一个,单例模式是最好的解决方案。

blob.jpg

在GOF的《设计模式:可复用面向对象软件的基础》中是这样说的:保证一个类只有一个实例,并提供一个访问它的全局访问点。首先,需要保证一个类只有一个实例;在类中,要构造一个实例,就必须调用类的构造函数,如此,为了防止在外部调用类的构造函数而构造实例,需要将构造函数的访问权限标记为protected或private;最后,需要提供要给全局访问点,就需要在类中定义一个static函数,返回在类内部唯一构造的实例。

实现要点

单例模式的要点有三个:

  1. 某个类只能有一个实例
  2. 它必须自行创建这个实例
  3. 它必须自行向整个系统提供这个实例

从具体实现角度来说,就是以下三点:

  1. 单例模式的类只提供私有的构造函数
  2. 类定义中含有一个该类的静态私有对象
  3. 该类提供了一个静态的共有的函数用于创建或获取它本身的静态私有对象

注意事项

单例模式,赋值构造函数和拷贝构造函数都要声明为私有的,以便防止这类赋值的动作产生。

返回最好是返回引用,要不然用户会不小心删除掉指针的(如果非要返回指针,最好将析构函数声明为私有的

最简单实现:

#include <iostream>


class Singleton
{
public :
    static Singleton* GetInstance( )           // 获取对象单例的指针
    {
        if(Singleton::m_singleton == NULL)       // 如果单例对象没有创建, 则将其创建
        {
            Singleton::m_singleton = new Singleton( );
        }

        return Singleton::m_singleton;
    }
    static void DestroyInstance( )                  // 销毁单例对象的空间
    {
        if(Singleton::m_singleton != NULL)
        {
            delete Singleton::m_singleton;
            Singleton::m_singleton = NULL;
        }
    }
private :
    Singleton( )                                // 构造函数[被保护]
    {
    }

    Singleton(const Singleton &singleton)       // 赋值构造函数[被保护]
    {
    }

    ~Singleton()                                // 析构函数[被保护,外部无法通过delete删除对象]
    {
    }

    static Singleton *m_singleton;                // 指向单例对象的指针
};

////////////////////
Singleton* Singleton::m_singleton = NULL;                // 指向单例对象的指针
////////////////////



int main()
{
    Singleton *sp1 = Singleton::GetInstance( );
    Singleton *sp2 = Singleton::GetInstance( );

    std::cout <<(sp1 == sp2) <<std::endl;           // 两个对象的地址是相同的

    Singleton::DestroyInstance( );

    return 0;
}

这是最简单的实现,但是这种实现方式有很多问题,比如没有考虑多线程的问题,再多线程的情况下,就可能创建多个Singleton实例,以下版本是改善的版本。

#include <iostream>

class Singleton
{
public:
    static Singleton* GetInstance()
    {
        // 此处进行了两次m_Instance == NULL的判断,
        // 是借鉴了Java的单例模式实现时,
        // 使用的所谓的“双检锁”机制。
        // 因为进行一次加锁和解锁是需要付出对应的代价的,
        // 而进行两次判断,就可以避免多次加锁与解锁操作,
        // 同时也保证了线程安全
        if(Singleton::m_singleton == NULL)
        {
            Lock();         // 此处可以调用其他库的锁线程,或者自己实现一个
            if(Singleton::m_singleton == NULL)
            {
                Singleto::m_singleton = new Singleton();
            }
            Unlock();       // 此处可以调用其他库的锁线程,或者自己实现一个
        }
    }

    static void DestroyInstance()       // 销毁单例对象的空间
    {
        Lock();         // 加锁是为了防止多线程重复释放
        if(Singleton::m_singleton != NULL)
        {
            delete Singleton::m_singleton;
            Singleton::m_singleton = NULL;
        }
        Unlock();
    }

private:

    Singleton( )                                // 构造函数[被保护]
    {

    }

    Singleton(const Singleton &singleton)       // 赋值构造函数[被保护]
    {

    }

    ~Singleton( )                               // 析构函数
    {
    }

    static Singleton *m_singleton;                // 指向单例对象的指针
};

int main()
{
    Singleton* sp1 = Singleton::GetInstance();
    Singleton *sp2 = Singleton::GetInstance();

    std::cout <<(sp1 == sp2) <<std::endl;

    Singleton::DestroyInstance();

    return 0;
}

这种实现方式再平时的项目开发种用的很好,也没有什么问题,但是如果进行大数据的操作,加锁操作将成为一个性能瓶颈,为此,一种新的单例模式的实现也就出现了。

外部实例化

#include <iostream>

class Singleton
{
public:
    static Singleton* GetInstance()
    {
        return const_cast<Singleton*>(Singleton:m_singleton);
    }

    static void DestroyInstance()
    {
        if(Singleton::m_singleton != NULL)
        {
            delete Singleton::m_singleton;
            Singleton::m_singleton = NULL;
        }
    }

private:
    Singleton(){}       // 构造
    Singleton(const Singleton &singleton){}     // 拷贝构造
    ~Singleton(){}
    static Singleton* m_singleton;
}

///////
Singleton* Singleton::m_singleton = new Singleton();        // 外部初始化
//////


int main()
{
    Singleton *sp1 = Singleton::GetInstancePoint();
    Singleton *sp2 = Singleton::GetInstancePoint();

    std::cout <<(sp1 == sp2) <<std::endl;

    Singleton::DestroyInstance( );

    return 0;
}

内存泄漏问题

在上述的实现中,都是采用new操作符实现实例化对象的,为了避免内存泄漏,我们是添加了一个DestoryInstance的static函数,在这里进行delete操作,这也是最简单,最普通的处理方法了;

但是,很多时候,我们是很容易忘记调用DestoryInstance函数,就像你忘记了调用delete操作一样。

#include <iostream>

class Singleton
{
public :
    static Singleton* GetInstancePoint( )           // 获取对象单例的指针
    {
        return const_cast<Singleton *>(Singleton::m_singleton);
    }
private :
    Singleton( )                                // 构造函数[被保护]
    {
    }

    Singleton(const Singleton &singleton)       // 赋值构造函数[被保护]
    {
        std::cout <<1221 <<std::endl;
    }

    ~Singleton( )
    {
    }


    static Singleton *m_singleton;                // 指向单例对象的指针


    class GC
    {
      public :
        ~GC( )
        {
            if (Singleton::m_singleton != NULL)
            {
                std::cout<< "Here destroy the m_singleton..." <<std::endl;
                delete m_singleton;
                m_singleton = NULL ;
            }
        }
        static GC gc;
    };
};


Singleton* Singleton::m_singleton = new Singleton( );
Singleton::GC Singleton::GC::gc;

int main()
{
    Singleton *sp1 = Singleton::GetInstancePoint( );
    Singleton *sp2 = Singleton::GetInstancePoint( );

    std::cout <<(sp1 == sp2) <<std::endl;

    return 0;
}

程序运行结束时,系统会调用Singleton的静态成员GC的析构函数,该析构函数会进行资源的释放,而这种资源的释放方式是在程序员“不知道”的情况下进行的,而程序员不用特别的去关心,使用单例模式的代码时,不必关心资源的释放。


转载自:
https://blog.csdn.net/gatieme/article/details/17998135

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!

发表评论

电子邮件地址不会被公开。 必填项已用*标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据