有时候实现了一个类,但只需要创建出一个实例化的对象就能完成需求,如果有太多的对象不仅浪费内存空间也会使得代码数据不那么好维护,因此会需要设计出一个只能生成一个实例的类;
首先,要使得这个类只能实例化出一个对象,那么它的构造函数肯定不能够被外部随意调用,因此应该将类的构造函数的访问限定符设定为私有的,但是这样的话,如何获得唯一一个的实例化对象呢?可以设计一个成员函数,这个成员函数只能分配出唯一一个该类对象的内存空间,并且返回指向这块空间的指针给申请的对象,因此,可以将类设计为如下:
#include <iostream> using namespace std; class Singleton { private: Singleton() {} Singleton(Singleton& s); Singleton& operator=(Singleton& s); public: static Singleton*& GetInstance() { if(_instance == NULL) { _instance = new Singleton(); } return _instance; } ~Singleton() { if(_instance != NULL) delete _instance; } private: static Singleton *_instance; }; Singleton* Singleton::_instance = NULL;
上面的栗子中将构造函数、拷贝构造和赋值运算符的重载函数的访问限定符置为私有的,外界不能通过这些函数实例化出对象;然后设计了一个静态成员函数,这里值得一提的是,静态的成员函数没有隐含的this指针,因此外界可以通过类名加域的访问符号::来调用静态的成员函数;
类的成员变量只定义了一个指向类对象的一个_instance指针,静态的成员函数可以在类外部初始化,先将其初始化为NULL,当第一次调用这个GetInstance函数的时候,_instance为NULL,因此为其分配一个类对象的空间并将其返回;但是当第二次调用这个函数的时候,_instance已经有了值并不会再分配空间而是返回已经存在的实例化出的对象,因此无论怎么调用GetInstance函数都只会返回同一块地址空;
但是上面的函数在单线程环境下运行是没有问题的,当有多个线程并发访问这个函数的时候,如果两个或多个线程同时拿到了初始化为NULL的_instance的值要进行判断的时候,有可能都会判断成功,也就是会new出来两块不同的地址空间,这样就不符合设计的初衷了,因此在多线程运行环境下,GetInstance函数中的代码就会成为临界区,也就是要有互斥的关系,可以为其加上mutex互斥锁:
static Singleton*& GetInstance() { pthread_mutex_lock(&lock); if(_instance == NULL) { _instance = new Singleton(); } pthread_mutex_unlock(&lock); return _instance; }
可是上面的代码还是存在一些效率的问题,如果一个线程最开始获得了锁并且成功new出了空间,那么之后的线程每一次进到函数GetInstance里面都要争夺一下锁资源并且再依次判断,所以,可以在加锁之前就先进行一次判断,如果_instance不为空,后面就没有必要竞争锁资源再进行判断了,所以代码可以优化为如下:
static Singleton*& GetInstance() { if(_instance == NULL) { pthread_mutex_lock(&lock); if(_instance == NULL) { _instance = new Singleton(); } pthread_mutex_unlock(&lock); } return _instance; }
其实除了上面所给出的解法,还有另外一种简单粗暴的设计方式,那就是直接在给静态成员变量_instance初始化的时候就初始化为new出来的一个类的实例化对象,之后每一次调用GetInstance函数获取_instance的值的时候就直接返回:
class Singleton { private: Singleton() {} Singleton(Singleton& s); Singleton& operator=(Singleton& s); public: static Singleton*& GetInstance() { return _instance; } private: static Singleton *_instance; }; Singleton* Singleton::_instance = new Singleton();
上面一次性就将对象空间给开辟出来每次不用判断就直接返回,这种方式被称为饿汉式,相当于用空间换时间;而前面一种在需要的时候去判断然后开辟空间,这种方式被叫做懒汉式,就相当于用时间来换空间了,各有利弊。
《完》
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。