C++ 中**单例模式(Singleton Pattern)**的完整、现代、实用的各种实现方式详解,按从简单到高级的顺序排列,每种都附带:
- 完整可编译代码
- 优缺点分析
- 适用场景
- 是否线程安全
- 是否推荐使用
1. 最经典(但已过时)的饿汉式(Eager Initialization)
// singleton_eager.h
#pragma once
#include <iostream>
class SingletonEager {
private:
SingletonEager() {
std::cout << "饿汉式单例构造\n";
}
~SingletonEager() {
std::cout << "饿汉式单例析构\n";
}
// 禁用拷贝和移动
SingletonEager(const SingletonEager&) = delete;
SingletonEager& operator=(const SingletonEager&) = delete;
SingletonEager(SingletonEager&&) = delete;
SingletonEager& operator=(SingletonEager&&) = delete;
public:
static SingletonEager& getInstance() {
static SingletonEager instance; // 静态局部变量,程序启动时就创建
return instance;
}
void doSomething() {
std::cout << "饿汉式单例正在工作...\n";
}
};
优点:实现最简单,线程安全(C++11 后静态局部变量初始化是线程安全的)
缺点:程序启动时就创建,即使永远用不到也会占用内存
适用:确定一定会用到,且构造代价小的场景
2. 现代推荐写法(C++11 后)—— 局部静态变量(Meyers’ Singleton)
这是目前最推荐的单例写法(Scott Meyers 提出),简洁、安全、延迟初始化。
// singleton_meyers.h
#pragma once
#include <iostream>
class SingletonMeyers {
private:
SingletonMeyers() {
std::cout << "Meyers 单例构造\n";
}
~SingletonMeyers() {
std::cout << "Meyers 单例析构\n";
}
// 禁用拷贝和移动
SingletonMeyers(const SingletonMeyers&) = delete;
SingletonMeyers& operator=(const SingletonMeyers&) = delete;
SingletonMeyers(SingletonMeyers&&) = delete;
SingletonMeyers& operator=(SingletonMeyers&&) = delete;
public:
static SingletonMeyers& getInstance() {
// C++11 之后,静态局部变量的初始化是线程安全的
// 只会在第一次调用时初始化,且只初始化一次
static SingletonMeyers instance;
return instance;
}
void doSomething() {
std::cout << "Meyers 单例正在工作...\n";
}
};
使用示例:
#include "singleton_meyers.h"
int main() {
auto& s1 = SingletonMeyers::getInstance();
auto& s2 = SingletonMeyers::getInstance();
s1.doSomething();
std::cout << (&s1 == &s2 ? "是同一个实例" : "不是同一个实例") << std::endl;
return 0;
}
输出:
Meyers 单例构造
Meyers 单例正在工作...
是同一个实例
这是目前最推荐的单例写法,除非有特殊需求,否则直接使用这个。
3. 双检锁(Double-Checked Locking)——传统写法(不推荐)
// singleton_dcl.h(不推荐在现代 C++ 中使用)
#pragma once
#include <mutex>
#include <iostream>
class SingletonDCL {
private:
static SingletonDCL* instance_;
static std::mutex mutex_;
SingletonDCL() { std::cout << "DCL 单例构造\n"; }
~SingletonDCL() { std::cout << "DCL 单例析构\n"; }
SingletonDCL(const SingletonDCL&) = delete;
SingletonDCL& operator=(const SingletonDCL&) = delete;
public:
static SingletonDCL* getInstance() {
// 第一重检查(无锁)
if (instance_ == nullptr) {
std::lock_guard<std::mutex> lock(mutex_);
// 第二重检查(加锁)
if (instance_ == nullptr) {
instance_ = new SingletonDCL();
}
}
return instance_;
}
void doSomething() {
std::cout << "DCL 单例工作\n";
}
};
// 静态成员初始化
SingletonDCL* SingletonDCL::instance_ = nullptr;
std::mutex SingletonDCL::mutex_;
严重警告:
在 C++11 之前,双检锁存在内存重排序问题(指令重排),可能导致线程安全隐患。
C++11 之后虽然 new 有内存栅栏,但仍然不推荐,因为写法复杂且容易出错。
结论:永远不要再写双检锁单例,直接用第 2 种 Meyers 写法即可。
4. 单例模板(可复用写法)
当项目中有多个单例类时,可以用模板封装。
// singleton_template.h
#pragma once
template<typename T>
class Singleton {
protected:
Singleton() = default;
virtual ~Singleton() = default;
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
public:
static T& getInstance() {
static T instance;
return instance;
}
};
// 使用示例
class Logger : public Singleton<Logger> {
public:
void log(const std::string& msg) {
std::cout << "[LOG] " << msg << std::endl;
}
};
int main() {
Logger::getInstance().log("这是一个日志");
return 0;
}
5. 带资源的单例(需要手动销毁)
某些单例持有文件句柄、数据库连接、线程等资源,需要显式销毁。
// singleton_with_destroy.h
#pragma once
#include <iostream>
class DatabaseConnection : public Singleton<DatabaseConnection> {
public:
void connect() { std::cout << "连接数据库...\n"; }
void disconnect() { std::cout << "断开数据库连接\n"; }
private:
friend class Singleton<DatabaseConnection>;
DatabaseConnection() { connect(); }
~DatabaseConnection() { disconnect(); }
};
// 程序结束时自动调用析构函数释放资源
6. Qt 项目中的单例写法(推荐)
在 Qt 中,推荐结合 QObject 使用单例:
// appsettings.h
#pragma once
#include <QObject>
#include <QSettings>
class AppSettings : public QObject {
Q_OBJECT
Q_DISABLE_COPY(AppSettings)
public:
static AppSettings& instance() {
static AppSettings inst;
return inst;
}
QString getTheme() const { return settings_.value("theme", "light").toString(); }
void setTheme(const QString& theme) { settings_.setValue("theme", theme); }
private:
explicit AppSettings(QObject* parent = nullptr)
: QObject(parent), settings_("MyCompany", "MyApp") {}
QSettings settings_;
};
使用:
AppSettings::instance().setTheme("dark");
qDebug() << AppSettings::instance().getTheme();
总结:2025–2026 年单例模式推荐排序
| 排名 | 写法 | 线程安全 | 延迟初始化 | 推荐指数 | 说明 |
|---|---|---|---|---|---|
| 1 | 局部静态变量(Meyers) | 是 | 是 | ★★★★★ | 最简洁、最安全、现代 C++ 首选 |
| 2 | 单例模板(模板封装) | 是 | 是 | ★★★★☆ | 多单例场景首选 |
| 3 | Qt 项目专用(QObject + 静态) | 是 | 是 | ★★★★☆ | Qt 生态最佳实践 |
| 4 | 带资源管理的单例 | 是 | 是 | ★★★☆☆ | 需要手动释放资源时使用 |
| 5 | 双检锁(DCL) | 有风险 | 是 | ★☆☆☆☆ | 不要再用,容易出错 |
一句话总结:
现代 C++ 项目中,99% 的单例都应该直接写成第 1 种(Meyers 单例),简单、安全、优雅。
如果您需要:
- 某个写法的完整 VS 项目(.sln + .vcxproj)
- Qt 项目中单例 + QSettings + QML 绑定的完整示例
- 单例的反模式与替代方案(依赖注入)
的完整、现代、实用的各种实现方式详解&spm=1001.2101.3001.5002&articleId=157511935&d=1&t=3&u=8be9d2c7cc5e48db9adf4f09ad81482f)
1411

被折叠的 条评论
为什么被折叠?



