单例模式之C++实现

2019-04-14 20:05发布

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