【C++ | 设计模式】单例模式的详解与实现
1.简述
单例模式(Singleton Pattern)是设计模式中最简单的形式之一,其目的是使得类的一个对象成为系统中的唯一实例。
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一对象的方式,可以直接访问,不需要实例化该类的对象。
2.UML 结构图

3.要点
单例模式的要点有三个:
-
单例类有且仅有一个实例
-
单例类必须自行创建自己的唯一实例
-
单例类必须给所有其他对象提供这一实例
从具体实现角度来说,可分为以下三点:
-
提供一个 private 构造函数(防止外部调用而构造类的实例)
-
提供一个该类的 static private 对象
-
提供一个 static public 函数,用于创建或获取其本身的静态私有对象(例如:GetInstance())
除此之外,还有一些关键点(需要多加注意,很容易忽视):
-
线程安全(双检锁 - DCL,即:double-checked locking)
-
资源释放
4.单例模式的实现
4.1 单例 - 懒汉式(非线程安全)
在这种实现中,实例在第一次调用 GetInstance 时创建。由于没有线程安全机制,这种方式不适用于多线程环境。
#include <iostream>
class Singleton {
public:
static Singleton* GetInstance();
private:
Singleton() {} // 构造函数(私有)
static Singleton* p; // 指向单例对象的指针
};
// 初始化静态成员变量
Singleton* Singleton::p = nullptr;
// 获取单例实例
Singleton* Singleton::GetInstance() {
if (p == nullptr) {
p = new Singleton();
}
return p;
}
int main() {
Singleton* instance1 = Singleton::GetInstance();
Singleton* instance2 = Singleton::GetInstance();
std::cout << (instance1 == instance2) << std::endl; // 输出1,两个实例是相同的
return 0;
}
懒汉式的特点:
-
Lazy 初始化
-
非多线程安全
优点:第一次调用才初始化,避免内存浪费。
缺点:必须加锁(在“线程安全”部分分享如何加锁)才能保证单例,但加锁会影响效率。
4.2 单例 - 饿汉式(线程安全)
在这种实现中,单例对象在类加载时就被创建,确保了线程安全,但可能导致启动时的资源浪费。
#include <iostream>
class Singleton {
public:
static Singleton* GetInstance();
private:
Singleton() {} // 构造函数(私有)
static Singleton* p; // 指向单例对象的指针
};
// 初始化静态成员变量
Singleton* Singleton::p = new Singleton(); // 在类加载时就创建实例
// 获取单例实例
Singleton* Singleton::GetInstance() {
return p;
}
int main() {
Singleton* instance1 = Singleton::GetInstance();
Singleton* instance2 = Singleton::GetInstance();
std::cout << (instance1 == instance2) << std::endl; // 输出1,两个实例是相同的
return 0;
}
饿汉式的特点:
-
非 Lazy 初始化
-
多线程安全
优点:没有加锁,执行效率会提高。
缺点:类加载时就初始化,浪费内存。
4.3 单例 - 懒汉式(双重检查锁,线程安全)
这种实现使用双重检查锁定(DCL)机制来确保线程安全,并且仅在需要时才创建单例对象。
#include <iostream>
#include <mutex>
class Singleton {
public:
static Singleton* GetInstance();
private:
Singleton() {} // 构造函数(私有)
static Singleton* p; // 指向单例对象的指针
static std::mutex m_mutex; // 互斥锁,用于线程安全
};
// 初始化静态成员变量
Singleton* Singleton::p = nullptr;
std::mutex Singleton::m_mutex;
// 获取单例实例
Singleton* Singleton::GetInstance() {
if (p == nullptr) { // 第一次检查
std::lock_guard<std::mutex> lock(m_mutex); // 锁定互斥量
if (p == nullptr) { // 第二次检查
p = new Singleton();
}
}
return p;
}
int main() {
Singleton* instance1 = Singleton::GetInstance();
Singleton* instance2 = Singleton::GetInstance();
std::cout << (instance1 == instance2) << std::endl; // 输出1,两个实例是相同的
return 0;
}
需要进行两次检验的原因:
第一次判断是为了验证是否创建对象,第二次判断是为了避免重复创建单例,因为可能会存在多个线程通过了第一次判断在等待锁,进 而创建新的实例对象。
-
当A与B同时调用GetInstance时,判断第一个if都为空,这时A拿到锁,进行第二层if判断,条件成立new了一个对象;
-
B在外层等待,A创建完成,释放锁,B拿到锁,进行第二层if判断,条件不成立,结束释放锁;
-
C调用GetInstance时第一层判断不成立,直接拿到singleton对象返回,避免进入锁,减少性能开销。
4.4单例 - 局部静态变量实现
#include <iostream>
class Singleton {
public:
// 获取单例实例
static Singleton& GetInstance() {
static Singleton instance; // 局部静态变量
return instance;
}
// 禁止拷贝构造函数和赋值运算符
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
// 示例方法
void ShowMessage() const {
std::cout << "Singleton instance address: " << this << std::endl;
}
private:
Singleton() {} // 私有构造函数
};
int main() {
Singleton& instance1 = Singleton::GetInstance();
Singleton& instance2 = Singleton::GetInstance();
instance1.ShowMessage(); // 输出单例对象的地址
instance2.ShowMessage(); // 输出单例对象的地址
// 验证两个实例是相同的
std::cout << "Are both instances the same? " << ( &instance1 == &instance2 ) << std::endl; // 输出1,两个实例是相同的
return 0;
}
在这种实现中,单例对象是静态局部变量,只有在首次调用 GetInstance 时才会被创建。由于局部静态变量的初始化是线程安全的,这种方式不需要额外的锁机制来确保线程安全。
5.单例模式的资源释放
5.1使用局部静态变量(自动释放)
#include <iostream>
class Singleton {
public:
static Singleton& GetInstance() {
static Singleton instance; // 局部静态变量
return instance;
}
// 禁止拷贝构造函数和赋值运算符
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
// 示例方法
void ShowMessage() const {
std::cout << "Singleton instance address: " << this << std::endl;
}
private:
Singleton() {
std::cout << "Singleton created" << std::endl;
}
~Singleton() {
std::cout << "Singleton destroyed" << std::endl;
}
};
int main() {
Singleton& instance1 = Singleton::GetInstance();
instance1.ShowMessage();
return 0;
}
5.2显式释放资源(使用懒汉式)
要手动释放资源,添加一个 static 函数,编写需要释放资源的代码,然后在需要释放的时候,手动调用该函数。
#include <iostream>
class Singleton {
public:
static Singleton* GetInstance() {
if (p == nullptr) {
p = new Singleton();
}
return p;
}
static void DestroyInstance() {
if(p != nullpter){
delete p;
p = nullptr;
}
}
// 禁止拷贝构造函数和赋值运算符
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
// 示例方法
void ShowMessage() const {
std::cout << "Singleton instance address: " << this << std::endl;
}
private:
Singleton() {
std::cout << "Singleton created" << std::endl;
}
~Singleton() {
std::cout << "Singleton destroyed" << std::endl;
}
static Singleton* p; // 指向单例对象的指针
};
// 初始化静态成员变量
Singleton* Singleton::p = nullptr;
int main() {
Singleton* instance1 = Singleton::GetInstance();
instance1->ShowMessage();
// 显式释放单例对象
Singleton::DestroyInstance();
return 0;
}
5.3 使用嵌套类进行资源释放
#include <iostream>
using namespace std;
class Singleton
{
public:
// 获取单例实例
static Singleton* GetInstance();
private:
Singleton() {
cout << "Singleton created" << endl;
} // 构造函数(私有)
// 嵌套类,用于管理资源释放
class GC
{
public:
~GC()
{
// 在析构函数中销毁单例对象
if (m_pSingleton != nullptr) {
cout << "Here destroy the m_pSingleton..." << endl;
delete m_pSingleton;
m_pSingleton = nullptr;
}
}
static GC gc; // 静态成员,用于在程序结束时自动销毁单例对象
};
static Singleton* m_pSingleton; // 指向单例对象的指针
};
// 初始化静态成员
Singleton* Singleton::m_pSingleton = nullptr;
Singleton::GC Singleton::GC::gc; // 实例化 GC 类的静态成员
// 获取单例实例
Singleton* Singleton::GetInstance()
{
if (m_pSingleton == nullptr)
{
m_pSingleton = new Singleton();
}
return m_pSingleton;
}
int main()
{
// 获取并使用单例实例
Singleton* instance1 = Singleton::GetInstance();
Singleton* instance2 = Singleton::GetInstance();
cout << "Are both instances the same? " << (instance1 == instance2) << endl; // 输出1,两个实例是相同的
// 当程序结束时,`GC` 的析构函数会被调用,自动释放单例对象的资源
return 0;
}
更多推荐
所有评论(0)