在软件开发中,有些资源通常只要生成一份,为了避免在程序中其他地方生成另外一份,或者生成另一份新的拷贝,必须在编码的时候利用语言编程技术来保证只会产生一份资源,而这样的编程技术称之为单例模式
根据单例的特点可以知道用C++来实现需要考虑的要点:
- 使得对象不能被拷贝、赋值;
- 所有的构造函数必须私有(这样就不会让 A a; A *p = new A; 通过编译);
- 但要保证在某处生成一个这样的对象;
第一条实现方式是通过把拷贝构造、拷贝赋值函数设为private类型;第二条实现方式跟第一条一样既把构造函数设为private类型;第三条貌似与第二条产生了冲突,确实如此,但可以通过友元的方式来实现。
首先来写一个Uncopyable类,大致如下:
class Uncopyable {
protected:
Uncopyable(){}
~Uncopyable(){}
private:
/*
* 以下都只给出声明,这样的话如果在Uncopyable的成员函数或者是友元函数或类中方式拷
* 贝,则编译器在链接阶段会因为找不到定义而报错,同样阻止了这一类比较特别拷贝赋值操
* 作,当然这里主要是阻止 A a(b), A a=b,a=b 这种情况的发生(A是Uncopyable的派生类)
*/
Uncopyable(const Uncopyable&);
const Uncopyable& operator=(const Uncopyable&);
}
接着来写一个单例的模板父类:
tempalte<class T>
class Singleton {
public:
static T * getInstance() {
static T instance;
return &instance;
}
}
然后写一个用例,假设一个类A我只让它产生一个对象:
#include <iostream>
using namespace std;
class A: public Uncopyable {
friend class Singleton<A>;//使得类Singleton中可以调用所有A的私有构造函数
private:
A(){}
... // 其他类型的构造函数
};
int main()
{
A * a_ptr = Singleton<A>::getInstance();
cout << ( a_ptr == Singleton<A>::getInstance())<<endl; //编译通过,结果为1,也就是同一个指针
A b; //编译报错,提示A的构造函数私有
A c(*a_ptr);//编译错误,提示Uncopyable的拷贝构造函数是私有的
A d = *a_ptr;//同上,因为声明赋值同样是调用的拷贝构造函数
*a_ptr = *Singleton<A>::getInstance();//编译错误,提示operator=拷贝赋值函数为私有的
return 0;
}
以上都是c++11环境下编译验证;
虽然上面的单例模式确实做到了基本功能,但是还是有些问题:
1. 返回指针容易使得程序猿无意识的 delete
这个指针;
2. 多线程不是安全;
先来看看解决1的一个方案:
把Singleton::getInstance 返回指针变成返回引用
...
static T & getInstance(){
static T instance;
return instance;
}
...
在获取实例时则这么写:
...
A &a = Singleton<A>::getInstance();//这样的话应该没有程序猿会写 delete a这样的操作了把,即使写了,编译器也会提前告诉你
...
第2个多线程问题,就是单例中的懒汉与饿汗两种模式了。说下大致思路把,饿汗了就是在程序启动阶段就初始化一个A的对象,以后不管多个线程直接取就是了,懒汉了当地第一次使用getInstance()才初始化一个A的对象,以后每次都是取第一次初始化的那个A对象。从这里就可以看出前面举的例子就是懒汉模式,懒汉模式的解决方法一般采用加锁的形式避免多线程竞争,而饿汗模式就不用考虑多线程问题。
下面来把上面的改成饿汗模式:
新加一个饿汗模式的单例类
template<class T>
class SingletonEager{
public:
static T * getInstance(){
return instance;
}
private:
static T * instance;
}
template<class T>
T * SingletonEager<T>::instance = new T;
而我们的类A也只需要改成如下即可:
class A:public Uncopyable{
friend class SingletonEager<A>;
private:
A(){}
... //其他类型的构造函数
}
其他情况请读者思考吧, 如有问题请大家指正……