一、简介
1. 含义
单例模式是常见的设计模式之一,本质是:只提供唯一的类的实例,其具有全局变量的特点,在任意位置都可以使用该类提供的接口访问到其唯一的实例对象。
2. 特点
- 全局唯一实例:
static
特性 - 不允许用户声明并定义实例,构造函数权限为
private
- 禁止赋值和拷贝
- 用户使用接口获取实例:使用静态类成员函数
- 线程安全
二、单例模式实现
1. 饿汉式
该单例模式是指单例实例在类装载时就构建,并被立即执行初始化。
#include <iostream>
class HungrySingleton
{
private:
static HungrySingleton* instance;
HungrySingleton()
{
std::cout << "构造函数!" ;
}
~HungrySingleton()
{
std::cout << "析构函数!" ;
}
public:
static HungrySingleton* getInstance()
{
return instance;
};
};
HungrySingleton* HungrySingleton::instance = new HungrySingleton();
int main()
{
HungrySingleton* test = HungrySingleton::getInstance();
return 0;
}
- 优点:
- 线程安全
- 在类加载时创建静态对象,调用时反应速度快
- 缺点:
- 资源利用率不高,
getInstance()
可能永远都不会执行 - 如果类是多态的, 那静态成员变量初始化顺序无法保证
- 资源利用率不高,
2. 懒汉式
该单例模式直到使用时才会实例化对象,即调用geInstance()
方法时才会创建一个单例对象。
#include <iostream>
class LazySingleton
{
private:
static LazySingleton* m_InstancePtr;
private:
LazySingleton()
{
std::cout << "构造函数执行!";
}
LazySingleton(LazySingleton&) = delete;
LazySingleton& operator=(const LazySingleton&) = delete;
public:
~LazySingleton()
{
std::cout << "析构函数执行!";
}
static LazySingleton* getInstance()
{
if (m_InstancePtr == nullptr)
{
m_InstancePtr = new LazySingleton();
}
return m_InstancePtr;
}
};
//静态成员:类内声明,类外初始化
LazySingleton* LazySingleton::m_InstancePtr = nullptr;
int main()
{
LazySingleton* test = LazySingleton::getInstance();
return 0;
}
- 优点: 不调用
getInstance()
就不会占用内存。 - 缺点: 懒汉模式在单线程中没问题,但多个线程同时访问时就可能创建多个实例,而且这些实例不是同一个对象,虽然后创建的实例会覆盖先创建的实例,但仍会存在拿到不同对象的情况。若想解决这个问题则需要加锁。
3. 局部静态变量
该单例模式通过局部静态变量的特性保证了线程安全 , 不需要使用共享指针、加锁等操作,代码更加简洁。
#include <iostream>
class MagicStaticSingleton
{
private:
MagicStaticSingleton()
{
std::cout << "构造函数执行!" << std::endl;
}
public:
~MagicStaticSingleton()
{
std::cout << "析构函数执行!" << std::endl;
}
MagicStaticSingleton(const MagicStaticSingleton&) = delete;
MagicStaticSingleton& operator=(const MagicStaticSingleton&) = delete;
static MagicStaticSingleton& getInstance()
{
static MagicStaticSingleton instance;
return instance;
}
};
int main()
{
MagicStaticSingleton& test = MagicStaticSingleton::getInstance();
return 0;
}
- 优点: 当变量在初始化时,并发同时进入声明语句,并发线程将会阻塞等待初始化结束。这样保证了并发线程在获取静态局部变量的时候一定是初始化过的,所以具有线程安全性。
4. 模板类实现
子类需要将自己作为模板参数T
传递给SingletonT
模板,并且需要将父类声明为友元,这样才能调用子类的私有构造函数。
#include <iostream>
template<typename T>
class SingletonT
{
public:
static T& getInstance()
{
static T instance;
return instance;
}
virtual ~SingletonT()
{
std::cout << "析构函数!" << std::endl;
}
SingletonT(const SingletonT&) = delete;
SingletonT& operator =(const SingletonT&) = delete;
protected:
SingletonT()
{
std::cout << "构造函数!" << std::endl;
}
};
class TestSingleton : public SingletonT<TestSingleton>
{
friend class SingletonT<TestSingleton>;
public:
TestSingleton(const TestSingleton&) = delete;
TestSingleton& operator =(const TestSingleton&) = delete;
private:
TestSingleton() = default;
};
int main()
{
TestSingleton& test = TestSingleton::getInstance();
return 0;
}
三、总结
推荐使用局部静态变量这种单例实现方式,不建议使用懒汉式,若需要单例复用则建议使用模板类实现。