一、单例模式的优点和缺点:
优点:
- 节约内存:保证内存里只有一个实例,减少内存的开销;
- 资源共用:可以避免对资源的多重占用;
- 访问自由:调用端可以随时访问单例类中的资源,起到优化和共享资源。
缺点:
- 扩展困难:单例类中的构造函数,其访问权限都是private,导致无法被继承;一般没有多余接口,如果需要接口扩展,只能修改单例类代码,别无他法,违背了设计七大原则中的【开闭原则】;
- 不利于代码调试:在并发测试中,如果单例模式没有执行完成,不能再生成一个新的对象;
- 违背单一指责原则:功能代码写在一个类中,如果功能设计不合理,则很容易违背【单一职责原则】。
二、单例模式的实现 :
1.懒汉式单例
1.1.懒汉式单例(有缺陷的):
类声明:
class LazySingleton
{
public:
~LazySingleton();
public:
/*
* 创建实例对象
*/
static LazySingleton* getInstance();
/*
* 成员函数
*/
void printfInfo()
{
std::cout << "I'm LazyInstance!" << std::endl;
}
/*
* 回收内存,销毁对象实例
*/
void destoryInstance();
private:
LazySingleton();
//~LazySingleton();
private:
static /*volatile*/ LazySingleton *m_instance;
};
说明:
- 构造函数访问权限一定是private的;
- 析构函数访问权限可以public,也可以private;
- 定义一个private的静态对象指针变量;
- 通过public的静态成员函数去初始化该对象指针,并返回获得该指针变量;
- static成员函数,只能访问static成员变量;static成员变量也可以被no static成员函数访问。
类实现:
/*volatile*/ LazySingleton* LazySingleton::m_instance = NULL;//静态成员变量,必须在类声明的外部初始化定义
LazySingleton::LazySingleton()
{
std::cout << "LazySingleton::LazySingleton()..." << std::endl;
}
LazySingleton::~LazySingleton()
{
std::cout << "LazySingleton::~LazySingleton()..." << std::endl;
}
LazySingleton* LazySingleton::getInstance()
{
if (NULL == m_instance)
{
m_instance = new LazySingleton();
std::cout << "Creat Object Instance..." << std::endl;
}
return m_instance;
};
void LazySingleton::destoryInstance()
{
if (m_instance)
{
std::cout << "Destory Object Instance..." << std::endl;
delete m_instance;
m_instance = NULL;
}
};
说明:
- 类的静态成员变量的初始化工作,必须放在类声明的外部;
- 对象指针创建以后,内存中有且只有一个存在。需要再次被创建,除非对已有对象指针进行销毁。
int main()
{
/*
* 单例对象的创建、调用和销毁
*/
std::cout << "------单例对象的创建、调用和销毁之【方法一】------" << std::endl;
LazySingleton::getInstance()->printfInfo();
LazySingleton::getInstance()->destoryInstance();
std::cout << std::endl << std::endl;
std::cout << "------单例对象的创建、调用和销毁之【方法二】------" << std::endl;
LazySingleton* pInstance = LazySingleton::getInstance();
if (pInstance)
{
pInstance->printfInfo();
delete pInstance;
pInstance = NULL;
}
system("pause");
return 0;
}
说明:
- 单例模式对象创建:在第一次调用getInstance方法时;
- 析构函数访问权限:如果调用destoryInstance方法销毁指针,可声明为private;如果使用delete销毁,则声明为public。
懒汉式单例的缺陷:
1.线程安全问题,当多线程获取单例时,会引发竞态条件:
- 第1个线程if条件判断m_instance是空,于是实例化单例;
- 同时第2个线程也尝试获取单例,这个时候if条件判断m_instance空的,于是也开始实例化单例。
- 解决方法:加锁
2.内存泄漏:
- 注意到类中只负责new出对象,却没有负责delete对象,因此只有构造函数被调用,析构函数却没有被调用;因此会导致内存泄漏。(上例中其实不会造成内存泄漏,不过需要手动去释放内存)
- 解决办法: 使用共享指针;
1.2.懒汉式单例(加锁、智能指针):
#include <memory>
#include <mutex>
class AdvancedLazySingleton
{
public:
typedef std::shared_ptr<AdvancedLazySingleton> Ptr;
~AdvancedLazySingleton();
public:
/*
* 创建实例对象
*/
static Ptr getInstance();
/*
* 成员函数
*/
void printfInfo()
{
std::cout << "I'm AdvancedLazySingleton!" << std::endl;
}
private:
AdvancedLazySingleton();
private:
static Ptr m_instance;//改进:智能指针
static std::mutex m_mutex;//改进:锁
};
AdvancedLazySingleton::Ptr AdvancedLazySingleton::m_instance = NULL;//静态成员变量,必须在类声明的外部初始化定义
std::mutex AdvancedLazySingleton::m_mutex;//注意:不可缺少
AdvancedLazySingleton::AdvancedLazySingleton()
{
std::cout << "AdvancedLazySingleton::AdvancedLazySingleton()..." << std::endl;
}
AdvancedLazySingleton::~AdvancedLazySingleton()
{
std::cout << "AdvancedLazySingleton::~AdvancedLazySingleton()..." << std::endl;
}
AdvancedLazySingleton::Ptr AdvancedLazySingleton::getInstance()
{
if (NULL == m_instance)
{
std::lock_guard<std::mutex> lk(m_mutex);
if (NULL == m_instance)
{
m_instance = std::shared_ptr<AdvancedLazySingleton>(new AdvancedLazySingleton);
std::cout << "Creat Object Instance..." << std::endl;
}
}
return m_instance;
};
shared_ptr和mutex都是C++11的标准。
1.以上这种方法的优点是:
- 基于 shared_ptr, 用了C++比较倡导的 RAII思想,用对象管理资源,当 shared_ptr 析构的时候,new 出来的对象也会被 delete掉。以此避免内存泄漏。
- 加了锁,使用互斥量来达到线程安全。这里使用了两个 if判断语句的技术称为双检锁;好处是,只有判断指针为空的时候才加锁,避免每次调用 get_instance的方法都加锁,锁的开销毕竟还是有点大的。
2.不足之处在于:
- 使用智能指针会要求用户也得使用智能指针,非必要不应该提出这种约束;
- 使用锁也有开销;
- 同时代码量也增多了,实现上我们希望越简单越好。
- 还有更加严重的问题,在某些平台(与编译器和指令集架构有关),双检锁会失效!具体可以看这篇文章,解释了为什么会发生这样的事情。
- 因此这里还有第三种的基于 Magic Staic的方法达到线程安全。
1.3. 最推荐的懒汉式单例(magic static )——局部静态变量
class BestLazySingleton
{
public:
~BestLazySingleton(){
std::cout<<"destructor called!"<<std::endl;
}
BestLazySingleton(const BestLazySingleton&)=delete;
BestLazySingleton& operator=(const BestLazySingleton&)=delete;
static BestLazySingleton& getInstance(){
static BestLazySingleton instance;
return instance;
}
private:
BestLazySingleton(){
std::cout<<"constructor called!"<<std::endl;
}
};
int main(int argc, char *argv[])
{
BestLazySingleton& instance_1 = BestLazySingleton::getInstance();
BestLazySingleton& instance_2 = BestLazySingleton::getInstance();
return 0;
}
这种方法又叫做 Meyers' SingletonMeyer's的单例, 是著名的写出《Effective C++》系列书籍的作者 Meyers 提出的。所用到的特性是在C++11标准中的Magic Static特性:
If control enters the declaration concurrently while the variable is being initialized, the concurrent execution shall wait for completion of the initialization.
如果当变量在初始化的时候,并发同时进入声明语句,并发线程将会阻塞等待初始化结束。
这样保证了并发线程在获取静态局部变量的时候一定是初始化过的,所以具有线程安全性。
C++静态变量的生存期 是从声明到程序结束,这也是一种懒汉式。
这是最推荐的一种单例实现方式:
- 通过局部静态变量的特性保证了线程安全 (C++11, GCC > 4.3, VS2015支持该特性);
- 不需要使用共享指针,代码简洁;
- 注意在使用的时候需要声明单例的引用
Single&
才能获取对象。
2.饿汉式单例:
类声明:
class HungrySingleton
{
public:
/*
* 获取对象
*/
static HungrySingleton& getInstance();
/*
* 成员函数
*/
void printfInfo()
{
std::cout << "I'm HungryInstance!" << std::endl;
}
private:
HungrySingleton();
~HungrySingleton();
static HungrySingleton m_instance;//静态对象,在程序运行时被立即初始化
};
说明:
- 饿汉式单例在类创建的同时就已经创建好一个静态的对象供系统使用,以后不再改变,所以是线程安全的,可以直接用于多线程而不会出现问题。
类实现:
HungrySingleton HungrySingleton::m_instance;//注意:不可缺少
HungrySingleton::HungrySingleton()
{
std::cout << "HungrySingleton::HungrySingleton()..." << std::endl;
}
HungrySingleton::~HungrySingleton()
{
std::cout << "HungrySingleton::~HungrySingleton()..." << std::endl;
}
HungrySingleton& HungrySingleton::getInstance()
{
return m_instance;
}
说明:
- 静态变量在类声明之外,必须在写一遍。
int main()
{
std::cout << "------单例对象的创建、调用和销毁之【方法一】------" << std::endl;
HungrySingleton::getInstance().printfInfo();
return 0;
}