C++单例singleton模式_legend

来源:互联网 发布:网络的利与弊400字 编辑:程序博客网 时间:2024/05/19 12:37


     单例模式 Singleton
一: 简介
 (一个类只有一个实例对象,用一个全局指针取访问它)


 单例模式的意图就是保证一个类仅有一个实例对象,该实例对象被所有的程序模块所共享。不同于全局对象,因为全局对象虽然可以被所以模块共享,但是不能保证只有一个对象,即可以创建一个局部对象来覆盖全局对象。
 应用:如系统日志的输出,GUI应用必须是单鼠标,操作系统只能有一个窗口管理器,一台PC连着一个键盘。

二: 思想

 单例模式通过类本身来管理其唯一实例,使用类的私有静态指针变量指向类的唯一实例,并用一个公有的静态方法获取该实例。

三: 应用

  (1)优点:

 1.单例模式在内存中只有一个实例,减少内存开支。
 适用于对象被频繁的创建,销毁。
 2.避免对资源的多重占用。
 如:一个写文件动作,由于只有一个实例在内存中,
 避免对同一个资源文件的同时写操作。

 3.用单例模式设置全局访问点。

  (2)缺点:
   1.单例模式没有接口,难以扩展。
   因为单例模式,要求自行实例化,并且提供单一实例,
   接口或者抽象类是无法被实例化的。

  (3)应用场景:
  在一个系统中,要求一个类只有一个对象、具体场景如下:
  (1.对象被频繁的创建,销毁 2.对象的创建需要消耗大量资源 3.共享数据)
   .要求生成唯一序列号的环境。

   .整个项目中需要一个共享数据。如Web 页面上的计数器,
   不需要每次刷新时都记录在数据库中,可以使用单例模式保持
   计数器的值,并确保是线程安全的 。

   .创建一个对象需要消耗的资源过多,如访问IO,访问数据库等资源。

   .需要定义大量的静态常量或者静态方法的环境,可以采用单例模式
   或者使用static .


四: 范例
 (1)范例一

 class CSingleton
{
private:
 CSingleton()   //构造函数是私有的
 {
 }
 static CSingleton *m_pInstance;
public:
 static CSingleton * GetInstance()
 {
  if(m_pInstance == NULL)  //判断是否第一次调用
   m_pInstance = new CSingleton();
  return m_pInstance;
 }
};

 解析一:

 1.用户访问唯一实例的方法只有GetInstance()成员函数:
 如果不通过这个函数,任何创建实例的尝试都将失败,因为类的构造函数是私有的。
 2.所有GetInstance()之后的调用都返回相同实例的指针:
 GetInstance()使用懒惰初始化,也就是说它的返回值是当这个函数首次被访问时被创建的。因为创建了之后m_pinstance就不在为null ,
 不会new 一个新的实例了。
 如:CSingleton* p1 = CSingleton :: GetInstance();
 CSingleton* p2 = p1->GetInstance();

 CSingleton & ref = * CSingleton :: GetInstance();

 (p1,p2的值是相同的。)

 3.扩展:
 单例模式singleton是一个类只有一个实例对象,可以稍加修改getInstance函数,该设计模板就可以适用于可变多实例情况,如一个类最多有五个实例。(五个私有static 静态成员对象指针,getInstacnce中每一个的操作与之类似。)

 4.特征:
   1)一个类只有一个实例对象
   2)私有static 成员对象指针m_instance;
   3) 私有构造函数
   4)公有的成员函数getInstance获取实例,返回m_instance;
   注意instance 中 的写法。

 5.问题:
 m_instance指向的对象什么时候释放?析构函数何时执行?

 6.释放该实例。

 可以在程序结束时调用GetInstance(),并对返回的指针掉用delete操作。这样做可以实现功能,但不仅很丑陋,而且容易出错。因为这样的附加代码很容易被忘记,而且也很难保证在delete之后,没有代码再调用GetInstance函数。

 一个妥善的方法是让这个类自己知道在合适的时候把自己删除,或者说把删除自己的操作挂在操作系统中的某个合适的点上,使其在恰当的时候被自动执行。


 7、线程不安全:

 解析:该单例模式在较低的并发情况下尚不会出现问题,若系统压力增大,并发量增加时则可能在内存中出现多个实例,破坏了最初的预期。如:一个线程A执行到singleton = new Singleton(),但还没有获得对象(对象初始化是需要时间的),第二个线程B也在执行,执行到(singleton == null)判断,那么B线程获得判断条件也是为真,于是继续运行下去,A线程获得了一个对象,B线程也获得了一个对象,在内存中就出现两个对象!


 8.线程不安全改进1:

 public class Singleton {
 
 private static final Singleton singleton = new Singleton();
 
 //限制产生多个对象
 
 private Singleton(){
 
 }
 
 //通过该方法获得实例对象
 
 public static Singleton getSingleton(){
 
 return singleton;
 
 }
 
 //类中其他方法,尽量是static
 
 public static void doSomething(){
 
 }
 
 }


 9,线程不安全改进2:

 注意:线程安全,采用互斥体的方式实现。

看代码:

//Emperor.h

#pragma once
#include <iostream>
using std::cout;
using std::endl;
using std::string;
class CEmperor
{
public:
    static CEmperor * GetInstance();
    static void ReleaseInstance();
    void EmperorInfo(void);
    void SetEmperorTag(string tag);
private:
    CEmperor(void);
    virtual ~CEmperor(void);
    CEmperor(const CEmperor&);
    CEmperor& operator=(const CEmperor&);
    static CEmperor *m_pEmperor;
    static HANDLE m_pMutex;
    string m_EmperorTag;
    class CGarbo
    {
    public:
        CGarbo()
        {
            cout << "Create Garbo" << endl;
        }
        ~CGarbo()
        {
            cout << "Destroy Garbo" << endl;
            if (NULL != m_pEmperor)
            {
                WaitForSingleObject(m_pMutex, INFINITE);
                if (NULL != m_pEmperor)
                {
                    cout << "Remove instance" << endl;
                    delete m_pEmperor;
                    m_pEmperor = NULL;
                }
                ReleaseMutex(m_pMutex);
            }
            if (NULL != m_pMutex)
            {
                cout << "Delete mutex" << endl;
                CloseHandle(m_pMutex);
                m_pMutex = NULL;
            }
        }
    };
    static CGarbo m_Garbo;
};
//Emperor.cpp
#include "StdAfx.h"
#include "Emperor.h"
#include <iostream>
using std::cout;
using std::endl;
using std::string;
CEmperor* CEmperor::m_pEmperor = NULL;
HANDLE CEmperor::m_pMutex = CreateMutex(NULL, FALSE, NULL);
CEmperor::CGarbo CEmperor::m_Garbo;
CEmperor::CEmperor(void)
{
    cout << "Create CEmperor Instance" << endl;
}
CEmperor::~CEmperor(void)
{
    cout << "Destroy CEmperor Instance and release its resource" << endl;
}
void CEmperor::EmperorInfo(void)
{
    char msgBuffer[50] = { 0 };
    sprintf_s(msgBuffer, 50, "皇ê帝?某3某3某3... ...(%s).", m_EmperorTag.c_str());
    string msg(msgBuffer);
    cout << msg.c_str() << endl;
}
CEmperor* CEmperor::GetInstance()
{
    if (NULL == m_pEmperor)
    {
        WaitForSingleObject(m_pMutex, INFINITE);
        if (NULL == m_pEmperor)
            m_pEmperor = new CEmperor();
        ReleaseMutex(m_pMutex);
    }
    return m_pEmperor;
}
void CEmperor::ReleaseInstance()
{
    if (NULL != m_pEmperor)
    {
        WaitForSingleObject(m_pMutex, INFINITE);
        if (NULL != m_pEmperor)
        {
            delete m_pEmperor;
            m_pEmperor = NULL;
        }
        ReleaseMutex(m_pMutex);
    }
}
void CEmperor::SetEmperorTag( string tag )
{
    m_EmperorTag = tag;
}
//Singleton.cpp
#include "stdafx.h"
#include "Emperor.h"
void DoIt()

{
    CEmperor *pEmperor1 = CEmperor::GetInstance();
    pEmperor1->SetEmperorTag("95");
    pEmperor1->EmperorInfo();

    CEmperor *pEmperor2 = CEmperor::GetInstance();
    pEmperor2->EmperorInfo();

    CEmperor *pEmperor3 = CEmperor::GetInstance();
    pEmperor3->EmperorInfo();

    CEmperor *pEmperor4 = pEmperor3;
    pEmperor4->EmperorInfo();

    CEmperor *pEmperor5 = NULL;
    pEmperor5 = pEmperor4;
    pEmperor5->EmperorInfo();

    CEmperor::ReleaseInstance();

}

int _tmain(int argc, _TCHAR* argv[])

{

    DoIt();


    _CrtSetDbgFlag(_CRTDBG_LEAK_CHECK_DF | _CRTDBG_ALLOC_MEM_DF);
    _CrtDumpMemoryLeaks();
    return 0;
}

 范例二:

 程序在结束的时候,系统会自动析构所有的全局变量。事实上,系统也会析构所有的类的静态成员变量,就像这些静态成员也是全局变量一样。利用这个特征,我们可以在单例类中定义一个这样的静态成员变量,而它的唯一工作就是在析构函数中删除单例类的实例。如下面的代码中的
 CGarbo类(Garbo意为垃圾工人):

class CSingleton
{
private:
 CSingleton()
 {
 }
 static CSingleton *m_pInstance;
 class CGarbo  
 //它的唯一工作就是在析构函数中删除CSingleton的实例
 {
 public:
  ~CGarbo()
  {
   if(CSingleton::m_pInstance)
    delete CSingleton::m_pInstance;
  }
 };
 static CGarbo Garbo; 
 //定义一个静态成员变量,程序结束时,系统会自动调用它的析构函数
public:
 static CSingleton * GetInstance()
 {
  if(m_pInstance == NULL)  //判断是否第一次调用
   m_pInstance = new CSingleton();
  return m_pInstance;
 }
};

解析二:
1.
类CGarbo被定义为CSingleton的私有内嵌类,以防该类被在其他地方滥用2.
程序运行结束时,系统会调用CSingleton的静态成员Garbo的析构函数,该析构函数会删除单例的唯一实例。
3.特征:
 1)一个类只有一个实例
 2)私有static 成员对象指针pInstance(初始化为null),私有构造函数。
 3)公有static成员函数getInstance 获取实例指针(注意getInstance 写法)。
 4)定义私有嵌套类,在嵌套类的析构函数中释放该单例对象。
 5)私有的静态成员嵌套类对象。


范例三:

class CSingleton
{
private:
 CSingleton()   //构造函数是私有的
 {
 }
public:
 static CSingleton & GetInstance()
 {
  static CSingleton instance;   //局部静态变量
  return instance;
 }
};
问题:
Singleton singleton = Singleton :: GetInstance();
这么做就出现了一个类拷贝的问题,这就违背了单例的特性。

改进:

禁止类拷贝和类赋值,禁止程序员用这种方式来使用单例、
GetInstance()函数返回一个指针而不是返回一个引用,函数的代码改为如下:

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

范例四:完整范例。

//Singleton.h
#ifndef _SINGLETON_H_
#define _SINGLETON_H_
#include <iostream>
using namespace std;
class Singleton
{
public:
static Singleton* Instance();
protected:
Singleton();
private:
static Singleton* _instance;
};
#endif //~_SINGLETON_H_


//Singleton.cpp
#include "Singleton.h"
#include <iostream>
using namespace std;
Singleton* Singleton::_instance = 0;
Singleton::Singleton()
{
cout<<"Singleton...."<<endl;
}
Singleton* Singleton::Instance()
{
if (_instance == 0)
{
_instance = new Singleton();
}
return _instance;
}

//main.cpp
#include "Singleton.h"
#include <iostream>
using namespace std;
int main(int argc,char* argv[])
{
Singleton* sgn = Singleton::Instance();
return 0;
}

五: 要点总结。
 1)要点:
   1.私有static 成员对象指针pInstance,私有构造函数
   2.私有static 属性,来给该单例实例设置属性。
   3.共有static 成员函数getInstance获取实例。
   共有static 成员函数doSomething;
   4.定义内嵌类,以及私有static 内嵌类对象,
   用内嵌类的析构函数来释放该单例实例。


六:单例模式扩展:有上限的多例模式。

(需要产生固定数量对象的模式就叫做有上限的多例模式,它是单例模式的一种扩展,采用有上限的多例模式,我们可以在设计时决定在内存中有多少个实例,方便系统进行扩展)

如:如读取文件,我们可以在系统启动时完成初始化工作,在内存中启动固定数量的reader实例,然后在需要读取文件时就可以快速响应。

如果一个类可以产生多个对象,对象的数量不受限制,直接使用new关键字就可以了,如果只要有一个对象,使用单例模式就可以了,但是如果要求一个类只能产生两个、三个对象呢?

范例:


public class Emperor {
 
//定义最多能产生的实例数量
 
private static int maxNumOfEmperor = 2;
 
//每个皇帝都有名字,使用一个ArrayList来容纳,每个对象的私有属性
 
private static ArrayList<String> nameList=new ArrayList<String>();
 
//定义一个列表,容纳所有的皇帝实例
 
private static ArrayList<Emperor> emperorList=new ArrayList<Emperor>();
 
//当前皇帝序列号
 
private static int countNumOfEmperor =0;
 
//产生所有的对象
 
static{
 
for(int i=0;i<maxNumOfEmperor;i++){
 
emperorList.add(new Emperor("皇"+(i+1)+"帝"));
 
}
 
}
 
private Emperor(){
 
//世俗和道德约束你,目的就是不产生第二个皇帝
 
}
 
//传入皇帝名称,建立一个皇帝对象
 
private Emperor(String name){
 
nameList.add(name);
 
}
 
//随机获得一个皇帝对象
 
public static Emperor getInstance(){
 
Random random = new Random();
 
countNumOfEmperor = random.nextInt(maxNumOfEmperor); //随机拉出一个皇帝,只要是个精神领袖就成
 
return emperorList.get(countNumOfEmperor);
 
}
 
//皇帝发话了
 
public static void say(){
 
System.out.println(nameList.get(countNumOfEmperor));
 
}
 
}


public class Minister {
 
public static void main(String[] args) {
 
//定义5个大臣
 
int ministerNum =5;
 
for(int i=0;i<ministerNum;i++){
 
Emperor emperor = Emperor.getInstance();
 
System.out.print("第"+(i+1)+"个大臣参拜的是:");
 
emperor.say();
 
}
 
}
 
}

结果:  
大臣参拜皇帝的结果如下所示。

第1个大臣参拜的是:皇1帝

第2个大臣参拜的是:皇2帝

第3个大臣参拜的是:皇1帝

第4个大臣参拜的是:皇1帝

第5个大臣参拜的是:皇2帝


要点:
1)私有static 成员对象指针数组pInstances.
私有构造函数。

2) 私有static 属性数组,为每一个实例设置属性。

如:此中nameList.
因为 name 作为 Emperor类的一个属性,在单例模式中,仅仅是name
,但是在扩展单例模式中,需要有多个实例,所以要有多个属性,
即属性数组。

0 0
原创粉丝点击