目录
6. 单例模式
单例模式顾名思义,保证一个类仅可以有一个实例化对象,并且提供一个可以访问它的全局接口。实现单例模式必须注意一下几点:
1. 单例类只能由一个实例化对象。
2. 单例类必须自己提供一个实例化对象。
3. 单例类必须提供一个可以访问唯一实例化对象的接口。
单例模式分为懒汉和饿汉两种实现方式。
6.1 懒汉单例模式
懒汉:故名思义,不到万不得已就不会去实例化类,也就是说在第一次用到类实例的时候才会去实例化一个对象。在访问量较小,甚至可能不会去访问的情况下,采用懒汉实现,这是以时间换空间。
6.1.1 非线程安全的懒汉单例模式
/*
* 关键代码:构造函数是私有的,不能通过赋值运算,拷贝构造等方式实例化对象。
*/
//懒汉式一般实现:非线程安全,getInstance返回的实例指针需要delete
class Singleton
{
public:
static Singleton* getInstance();
~Singleton(){}
private:
Singleton(){} //构造函数私有
Singleton(const Singleton& obj) = delete; //明确拒绝
Singleton& operator=(const Singleton& obj) = delete; //明确拒绝
static Singleton* m_pSingleton;
};
Singleton* Singleton::m_pSingleton = NULL;
Singleton* Singleton::getInstance()
{
if(m_pSingleton == NULL)
{
m_pSingleton = new Singleton;
}
return m_pSingleton;
}
6.1.2 线程安全的懒汉单例模式
std::mutex mt;
class Singleton
{
public:
static Singleton* getInstance();
private:
Singleton(){} //构造函数私有
Singleton(const Singleton&) = delete; //明确拒绝
Singleton& operator=(const Singleton&) = delete; //明确拒绝
static Singleton* m_pSingleton;
};
Singleton* Singleton::m_pSingleton = NULL;
Singleton* Singleton::getInstance()
{
if(m_pSingleton == NULL)
{
mt.lock();
if(m_pSingleton == NULL)
{
m_pSingleton = new Singleton();
}
mt.unlock();
}
return m_pSingleton;
}
6.1.3 返回一个reference指向local static对象
这种单例模式实现方式多线程可能存在不确定性:任何一种non-const static对象,不论它是local或non-local,在多线程环境下“等待某事发生”都会有麻烦。解决的方法:在程序的单线程启动阶段手工调用所有reference-returning函数。这种实现方式的好处是不需要去delete它。
class Singleton
{
public:
static Singleton& getInstance();
private:
Singleton(){}
Singleton(const Singleton&) = delete; //明确拒绝
Singleton& operator=(const Singleton&) = delete; //明确拒绝
};
Singleton& Singleton::getInstance()
{
static Singleton singleton;
return singleton;
}
6.2 饿汉单例模式
饿汉:饿了肯定要饥不择食。所以在单例类定义的时候就进行实例化。在访问量比较大,或者可能访问的线程比较多时,采用饿汉实现,可以实现更好的性能。这是以空间换时间。
//饿汉式:线程安全,注意一定要在合适的地方去delete它
class Singleton
{
public:
static Singleton* getInstance();
private:
Singleton(){} //构造函数私有
Singleton(const Singleton&) = delete; //明确拒绝
Singleton& operator=(const Singleton&) = delete; //明确拒绝
static Singleton* m_pSingleton;
};
Singleton* Singleton::m_pSingleton = new Singleton(); // 在这里就实例化
Singleton* Singleton::getInstance()
{
return m_pSingleton;
}
6.3 实例3
单例的一般实现比较简单,下面是代码和UML图。由于构造函数是私有的,因此无法通过构造函数实例化,唯一的方法就是通过调用静态函数GetInstance。
//Singleton.h
class Singleton
{
public:
static Singleton* GetInstance();
private:
Singleton() {}
static Singleton *singleton;
};
//Singleton.cpp
Singleton* Singleton::singleton = NULL;
Singleton* Singleton::GetInstance()
{
if(singleton == NULL)
singleton = new Singleton();
return singleton;
}
这里只有一个类,如何实现Singleton类的子类呢?也就说Singleton有很多子类,在一种应用中,只选择其中的一个。最容易就是在GetInstance函数中做判断,比如可以传递一个字符串,根据字符串的内容创建相应的子类实例。这也是DP书上的一种解法,书上给的代码不全。这里重新实现了一下,发现不是想象中的那么简单,最后实现的版本看上去很怪异。
//Singleton.h
#pragma once
#include <iostream>
using namespace std;
class Singleton
{
public:
static Singleton* GetInstance(const char* name);
virtual void Show() {}
protected: //必须为保护,如果是私有属性,子类无法访问父类的构造函数
Singleton() {}
private:
static Singleton *singleton; //唯一实例的指针
};
//Singleton.cpp
#include "Singleton.h"
#include "SingletonA.h"
#include "SingletonB.h"
Singleton* Singleton::singleton = NULL;
Singleton* Singleton::GetInstance(const char* name)
{
if(singleton == NULL)
{
if(strcmp(name, "SingletonA") == 0)
singleton = new SingletonA();
else if(strcmp(name,"SingletonB") == 0)
singleton = new SingletonB();
else
singleton = new Singleton();
}
return singleton;
}
//SingletonA.h
#pragma once
#include "Singleton.h"
class SingletonA: public Singleton
{
friend class Singleton; //必须为友元类,否则父类无法访问子类的构造函数
public:
void Show() { cout<<"SingletonA"<<endl; }
private: //为保护属性,这样外界无法通过构造函数进行实例化
SingletonA() {}
};
//SingletonB.h
#pragma once
#include "Singleton.h"
class SingletonB: public Singleton
{
friend class Singleton; //必须为友元类,否则父类无法访问子类的构造函数
public:
void Show(){ cout<<"SingletonB"<<endl; }
private: //为保护属性,这样外界无法通过构造函数进行实例化
SingletonB() {}
};
#include "Singleton.h"
int main()
{
Singleton *st = Singleton::GetInstance("SingletonA");
st->Show();
return 0;
}
上面代码有一个地方很诡异,父类为子类的友元,如果不是友元,函数GetInstance会报错,意思就是无法调用SingletonA和SIngletonB的构造函数。父类中调用子类的构造函数,我还是第一次碰到。当然了把SingletonA和SIngletonB的属性设为public,GetInstance函数就不会报错了,但是这样外界就可以定义这些类的对象,违反了单例模式。
看似奇怪,其实也容易解释。在父类中构建子类的对象,相当于是外界调用子类的构造函数,因此当子类构造函数的属性为私有或保护时,父类无法访问。为共有时,外界就可以访问子类的构造函数了,此时父类当然也能访问了。只不过为了保证单例模式,所以子类的构造函数不能为共有,但是又希望在父类中构造子类的对象,即需要调用子类的构造函数,这里没有办法才出此下策:将父类声明为子类的友元类。
参考资料:https://blog.csdn.net/wuzhekai1985/article/details/6665869