1.线程安全的懒汉单例模式
懒汉式的特点是延迟加载,比如配置文件,采用懒汉式的方法,顾名思义,懒汉么,很懒的,配置文件的实例直到用到的时候才会加载。。。。。。
class Singleton{
public:
static Singleton& GetInstance(void)
{
if(m_instance == nullptr) //双重check,实现多线程安全,不需每次调用都进行锁
{
pthread_mutex_lock(&mutex);
if(m_instance == nullptr)
{
cout <<"created new singleton!\n"<<endl;
m_instance = new Singleton();
atexit(Destroy);/*在通过new关键字创建类型实例的时候,我们同时通过atexit()函数注册了释放该实例的函数,从而保证了这些实例能够在程序退出前正确地析构。该函数的特性也能保证后被创建的实例首先被析构。其实,对静态类型实例进行析构的过程与前面所提到的在main()函数执行之前插入静态初始化逻辑相对应。*/
}
pthread_mutex_unlock(&mutex);
return *m_instance;
}
cout <<"The singleton was created already!\n"<<endl;
return *m_instance;
}
protected:
Singleton(){ pthread_mutex_init(&mutex,nullptr);}
~Singleton(){} /* 析构函数无论是protected还是priavte,其共同作用都是禁止在栈中产生对象,因为无法自动完成析构函数的调用,当然如果在堆上创建对象时,也不能直接delete对象了,因为这样也会在外部析构该对象,但是可以间接完成堆对象的析构。
构造函数定义为private后,意味着不仅仅不能在类的外部构造对象了,而且也不能在外部构造该类的子类的对象了,只能通过类的static静态函数来访问类的内部定义的对象,单件singleton模式就是私有构造函数的典型实例
构造函数定义为protected后,意味着不能在类的外部构造对象了,能在外部构造该类的子类的对象*/
private:
static Singleton* volatile m_instance;/*因为new运算符的调用分为分配内存、调用构造函数以及为指针赋值三步,就像下面的构造函数调用: SingletonInstance pInstance = new SingletonInstance();
“这行代码会转化为以下形式:”
SingletonInstance pHeap = __new(sizeof(SingletonInstance));
pHeap->SingletonInstance::SingletonInstance();
SingletonInstance pInstance = pHeap;
“这样转换是因为在C++标准中规定,如果内存分配失败,或者构造函数没有成功执行, new运算符所返回的将是空。一般情况下,编译器不会轻易调整这三步的执行顺序,但是在满足特定条件时,如构造函数不会抛出异常等,编译器可能出于优化的目的将第一步和第三步合并为同一步:”
1 SingletonInstance pInstance = __new(sizeof(SingletonInstance));
2 pInstance->SingletonInstance::SingletonInstance();
“这样就可能导致其中一个线程在完成了内存分配后就被切换到另一线程,而另一线程对Singleton的再次访问将由于pInstance已经赋值而越过if分支,从而返回一个不完整的对象。因此,我在这个实现中为静态成员指针添加了volatile关键字。该关键字的实际意义是由其修饰的变量可能会被意想不到地改变,因此每次对其所修饰的变量进行操作都需要从内存中取得它的实际值。它可以用来阻止编译器对指令顺序的调整。只是由于该关键字所提供的禁止重排代码是假定在单线程环境下的,因此并不能禁止多线程环境下的指令重排。”*/
static pthread_mutex_t mutex;
static void Destroy()
{
if(m_instance != NULL)
{
cout<<"delete Instance\n"<<endl;
delete m_instance;
m_instance = NULL;
}
}
Singleton(const Singleton& rhs){}
Singleton& operator=(const Singleton& rhs){}
};
pthread_mutex_t Singleton::mutex;
Singleton* volatile Singleton::m_instance = nullptr;
int main()
{
Singleton& pa=Singleton::GetInstance();
}
2.线程安全的懒汉可重用单例模式
可重用实现方法通常有三种:组合、派生以及模板。本文采用模板方法实现重用。
template<typename T>
class Singleton{
public:
static T* GetInstance(void)
{
if(m_instance ==nullptr)
{
pthread_mutex_lock(&mutex);
if(m_instance == nullptr)
{
m_instance = new T();
cout <<"created new singleton!\n"<<endl;
atexit(Destroy);
}
pthread_mutex_unlock(&mutex);
return m_instance;
}
cout <<"The singleton was created already!\n"<<endl;
return m_instance;
}
protected:
Singleton(){ pthread_mutex_init(&mutex,nullptr);}
~Singleton(){}
private:
static T* volatile m_instance;
static pthread_mutex_t mutex;
static void Destroy()
{
if(m_instance != NULL)
{
delete m_instance;
cout<<"delete Instance\n"<<endl;
m_instance = NULL;
}
}
Singleton(const Singleton& rhs){}
Singleton& operator=(const Singleton& rhs){}
};
template <typename T>
pthread_mutex_t Singleton<T>::mutex;
template <typename T>
T* volatile Singleton<T>::m_instance = nullptr;
int main()
{
//加入T为已定义的类
T* p2=Singleton<T>::GetInstance();
}
对于基于模板的单例模式,GetInstance()返回引用还是指针的问题:
由于经试验,当返回引用时,程序结束是T对象会析构两次,当返回指针时,对象T会析构一次。
具体原因不知道,希望高手解答。
3,饿汉单例模式
饿汉式的特点是一开始就加载了,如果说懒汉式是“时间换空间”,那么饿汉式就是“空间换时间”,因为一开始就创建了实例,所以每次用到的之后直接返回就好了。
class singleton
{
protected:
singleton() {}
~singleton() {}
private:
static singleton* m_pInstance;
class CGarbo //它的唯一工作就是在析构函数中删除CSingleton的实例
{
public:
~CGarbo()
{
if(singleton::m_pInstance)
{
cout<<"delete singleton\n"<<endl;
delete singleton::m_pInstance;
}
}
};
static CGarbo Garbo; //定义一个静态成员变量,程序结束时,系统会自动调用它的析构函数
public:
static singleton* GetInstance(){return m_pInstance;}
};
singleton* singleton::m_pInstance = new singleton();
singleton::CGarbo singleton::Garbo;