一、单例模式简单介绍
1.单例模式动机
有的时候,对于一个系统来说,我们无须创建多个实例。比如任务管理器,回收站。就拿任务管理器来说,我们只需要一个就可以了。如果创建两个不仅会占用内存,如果两个窗口内容不一致还会给用户造成误解。
为了节约系统资源,有时需要确保系统中某个类只有唯一一个实例,当这个唯一实例创建成功之后,我们无法再创建一个同类型的其他对象,所有的操作都只能基于这个唯一实例。
只包含一个单例角色:
(1)构造函数设计为私有
(2)删除拷贝构造函数和拷贝赋值
(3)在单例类内部定义一个Singleton类型的静态对象,并由静态方法getInstance函数返回。
二、单例模式分类
1.饿汉模式
在singleton类内声明一个singleton类静态的对象instance,在类外实现。这样在进入主函数之前这个实例就已经存在了。当我们调用getinstance方法的时候就可以得到这个实例了。
代码展示
//饿汉模式
class singleton
{
private:
singleton(){}
static singleton instance;
public:
~singleton(){}
static singleton* getinstance()
{
return &instance;
}
singleton& operator=(const singleton& s) = delete;
singleton(const singleton& s) = delete;
};
singleton singleton::instance;
测试
int main()
{
singleton* s1 = singleton::getinstance();
singleton* s2 = singleton::getinstance();
cout << s1 << endl;
cout << s2 << endl;
return 0;
}
结果
:可以看到返回的是一个实例。s1 == s2;
2.懒汉模式
所谓懒汉模式就是比较懒,什么时候用到这个实例,什么时候在进行创建。这个实例化对象可能过大,占用的空间大。我不想在进入主函数之前就把他创建出来,而是什么时候用到什么时候再创建。 不先期占用资源。懒汉模式是线程不安全的,需要加锁。
代码展示
:
//懒汉模式1
class singleton
{
private:
singleton() {}
singleton(const singleton& s) = delete;
singleton& operator=(const singleton&) = delete;
public:
~singleton() {}
static singleton* getinstance()
{
std::lock_guard<std::mutex> locker(g_mtx);
static singleton instance;
return &instance;
}
};
int main()
{
singleton* s1 = singleton::getinstance();
singleton* s2 = singleton::getinstance();
cout << s1 << endl;
cout << s2 << endl;
return 0;
}
//懒汉模式2
class singleton
{
private:
singleton(){}
singleton(const singleton&) = delete;
singleton& operator=(const singleton&) = delete;
public:
~singleton(){}
static singleton* getinstance()
{
static singleton* instance = nullptr; //静态变量只初始化一次
if (instance == nullptr)
{
std::lock_guard<mutex> locker(g_mtx);
instance = new singleton();
}
return instance;
}
};
为什么加锁?
如果不加锁的话,多线程情况下可能会出现问题。就比如说下面的这个情况,当两个线程都调用GetInstance函数。在data区构建对象的时候除了构建对象以外,还会在下面有一个标志位,当对象构建完成之后会将标志位从0改为1。当下次再构建的时候如果是1,就不构建了。即data区对象只初始化一次。但是多线程中,可能构建对象之后,0还没来得及变成1,线程1执行结束,变成线程2执行,这时候,线程2一看还是0,就又创建了一个对象。
如果我们想要好多单例类型,可以提供一个模板类实现代码复用。
代码
template<class T>
class singleton
{
protected:
singleton(){}
singleton(const singleton&) = delete;
singleton& operator=(const singleton&) = delete;
protected:
virtual ~singleton() { cout << "destory singleton" << endl; }
public:
static T* getinstance()
{
static T* instance = nullptr;
if (instance == nullptr)
{
instance = new T;
}
return instance;
}
};
class Myclass :public singleton<Myclass>
{
friend class singleton;
private:
int value;
Myclass(int x = 0) :value(x) {}
public:
~Myclass(){ cout << "destory Myclass" << endl; }
int& GetValue() { return value; }
};
//class Myclass2:public singleton<Myclass2>{...};
测试
int main()
{
Myclass* mp1 = Myclass::getinstance();
Myclass* mp2 = Myclass::getinstance();
mp1->GetValue() = 10;
cout << mp2->GetValue() << endl;
cout << mp1 << endl;
cout << mp2 << endl;
delete mp1;
return 0;
}
结果
:也是一个实例。
总结
1. 单例模式优点
由于在系统内存中只存在一个对象,因此可以节约系统资源,对于一些需要频繁创建和销毁的对象单例模式无疑可以提高系统的性能。
2.单例模式缺点
(1) 由于单例模式中没有抽象层,因此单例类的扩展有很大的困难。
(2) 单例类的职责过重,在一定程度上违背了“单一职责原则”。因为单例类既充当了工厂角色,提供了工厂方法,同时又充当了产品角色,包含一些业务方法,将产品的创建和产品的本身的功能融合到一起。
3.单例模式使用场景
(1) 系统只需要一个实例对象,如系统要求提供一个唯一的序列号生成器或资源管理器,或者需要考虑资源消耗太大而只允许创建一个对象。
(2) 客户调用类的单个实例只允许使用一个公共访问点,除了该公共访问点,不能通过其他途径访问该实例。