单例模式提供了一种创建对象的最佳方式,这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其对象的唯一方式,可以直接访问,不需要实例化该类的对象。
注意:1、单例只能有一个实例。2、单例类必须自己创建自己唯一的实例。3、单例类必须给所有的其他对象提供这一实例。
1. 介绍
意图:保证一个类只有一个实例,并提供一个访问他的全局访问点。
主要解决:一个全局使用的类平凡地创建与销毁。
何时使用:当您想控制实例数目,节省系统资源的时候。
如何解决:判断系统是否已经有这个单例,如果有则返回,如果没有则创建。
关键代码:构造函数是私有的
应用实例:1、一个班只有一个班主任
2、window是多进程多线程的,在操作一个文件的时候,就不避免地出现多个进程或线程同时操作一个文件的现象,所以所有文件的处理必须同故宫唯一的实例来进行。
3、一些设备管理器常常设计为单例模式,比如一个电脑有两台打印机,再输出的时候就要处理不能两台打印机打印同一个文件。
优点:1、在内存里是有一个实例,减少了内存的开销,尤其是平凡的出案件和销毁实例(比如管理学院网页首页缓存)。2、避免对资源的多重占用(比如写文件操作)。
缺点:没有借口,不能继承,与单一职责原则冲突,一个类应该之关系内部逻辑,而不关心外部怎么样来实例化。
使用场景:
- 要求生产唯一序列号
- WEB中的计数器,不用每次刷新都在数据库离家再一次,用单例先缓存起来。
- 创建一个对象需要消耗的资源过多,比如I/O与数据库的连接等。
2. 实现
2.1. C++实现
注意事项:getinstance()方法中需要使用同步锁防止多线程同时进入导致instance被多次实例化。
我们将创建一个 SingleObject 类。SingleObject 类有它的私有构造函数和本身的一个静态实例。
SingleObject 类提供了一个静态方法,供外界获取它的静态实例。SingletonPatternDemo 类使用 SingleObject 类来获取 SingleObject 对象。
#include <stdio.h>
#include <mutex>
#include <vector>
template < typename T > singleton{
public:
singleton(){};
~singleton(){};
static T* GetInstance() {
if (m_pInstance == nullptr)
{
std::lock_guard <std::mutex> m_mutex; // 双检查锁
if (m_pInstance == nullptr)
{
m_pInstance = new T();
}
}
return m_pInstance;
}
/* 比较简单粗暴的释放方式 */
static void free( void )
{
if( NULL != m_pInstance )
{
delete m_pInstance;
}
}
class{
public:
~Garbo()
{
if(singleton::m_pInstance != NULL)
{
delete m_pInstance;
}
}
}
static std::mutex m_mutex;
static T* m_pInstance;
/* 利用对象确定析构的原则进行内存释放 */
static Garbo m_garbo;
};
template < typename T > std::mutex singleton< T >::m_mutex;
template < typename T > T* singleton<T>::m_pInstance = nullptr;
class manage final : public singleton< manage > {
public:
manage() {};
~manage() {};
std::string& GetName() {
return name;
}
private:
unsigned int id;
std::string name;
};
#include <stdio.h>
#include <iostream>
#include <string>
#include "game.h"
int main(int argc, char** argv)
{
std::string mngName = manage::GetInstance()->GetName();
mngName = "Erichao";
std::string nowName = manage::GetInstance()->GetName();
std::cout << mngName << std::endl;
/* 面向过程方式进行内存释放 */
// manage* p = manage:GetInstance();
// delete p;
system("pause");
return 0;
}
2.2 单例模式析构函数
C++开发过程中,如果为给定析构函数,编译器会自动给我们提供一个析构函数。对于单例模式析构函数没有办法在继承子类中的动态函数对new的内存区域进行释放,主要是因为这种释放方式导致segment fault。产生原因是动态成员函数是依赖对象存在的,如果这个对象在本函数内部被释放,那么这个函数的this基础就不在了,导致访问内存的不确定性,系统崩溃。对于单例模式,释放方法目前可以有三种:
(1)在程序不需要这个类存在的时候通过GetInstance获取类地址,使用delete进行释放。这种方式的弊端是new和delete完全是分离开来,违背了面向对象的基本原理。
(2)类似于GetInstance方法,定义一个静态类方法,这个方法完成delete分配的内存空间。
(3)通过私有内嵌类的方法进行内存资源的释放。
对于第三种释放方法,在单例类Singleton中定义为Garbo的私有内嵌类,以防该类被在其他地方滥用。程序运行结束时,系统会调用Singleton的静态成员Garbo的析构函数,该析构函数会***单例的唯一实例使用这种方法释放单例对象的特征:
1、在单例类内部定义专有的嵌套类;
2、在单例类内定义私有的专门用于释放的静态成员;
3、利用程序在结束时析构全局变量的特性,选择最终的释放时机;
4、使用单例的代码不需要任何操作,不必关心对象的释放。
当然我们也可以直接使用智能指针的方式进行单例指针的释放,这种方式通过语言自己的特性来保证内存的恰当回收。
2.3. 多线程真正安全
Singleton模式中的实力构造器可以设置为protected以允许子类派生。
Singleton模式一般不要支持拷贝构造函数和clone接口,因为这有可能导致多个对象实例,与singleton模式的初衷违背。
如何实现多线程环境下安全的singleton?主义对双检查锁的正确实现。
//C++ 11版本之后的跨平台实现 (volatile)
std::atomic<Singleton*> Singleton::m_instance;
std::mutex Singleton::m_mutex;
Singleton* Singleton::getInstance() {
Singleton* tmp = m_instance.load(std::memory_order_relaxed);
std::atomic_thread_fence(std::memory_order_acquire);//获取内存fence
if (tmp == nullptr) {
std::lock_guard<std::mutex> lock(m_mutex);
tmp = m_instance.load(std::memory_order_relaxed);
if (tmp == nullptr) {
tmp = new Singleton;
std::atomic_thread_fence(std::memory_order_release);//释放内存fence
m_instance.store(tmp, std::memory_order_relaxed);
}
}
return tmp;
}