单例模式
单例模式是一种创建型设计模式,确保一个类只有一个实例,并提供一个全局访问点来访问这个实例。
基本概念
核心思想
- 一个类只能有一个实例
- 该实例必须由自身创建
- 必须向整个系统提供这个实例
实现方式
1. 懒汉式(线程不安全)
cpp
class Singleton {
private:
static Singleton* instance;
// 私有构造函数,防止外部实例化
Singleton() {}
// 防止拷贝和赋值
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
public:
static Singleton* getInstance() {
if (instance == nullptr) {
instance = new Singleton();
}
return instance;
}
void someBusinessLogic() {
// 业务逻辑
}
};
// 初始化静态成员
Singleton* Singleton::instance = nullptr;
问题:多线程环境下可能创建多个实例。
2. 懒汉式(线程安全 - 锁)
cpp
#include <mutex>
class ThreadSafeSingleton {
private:
static ThreadSafeSingleton* instance;
static std::mutex mtx;
ThreadSafeSingleton() {}
ThreadSafeSingleton(const ThreadSafeSingleton&) = delete;
ThreadSafeSingleton& operator=(const ThreadSafeSingleton&) = delete;
public:
static ThreadSafeSingleton* getInstance() {
std::lock_guard<std::mutex> lock(mtx); // 加锁
if (instance == nullptr) {
instance = new ThreadSafeSingleton();
}
return instance;
}
};
ThreadSafeSingleton* ThreadSafeSingleton::instance = nullptr;
std::mutex ThreadSafeSingleton::mtx;
缺点:每次获取实例都要加锁,性能较差。
3. 双重检查锁定(DCLP)
cpp
#include <atomic>
#include <mutex>
class DoubleCheckedSingleton {
private:
static std::atomic<DoubleCheckedSingleton*> instance;
static std::mutex mtx;
DoubleCheckedSingleton() {}
DoubleCheckedSingleton(const DoubleCheckedSingleton&) = delete;
DoubleCheckedSingleton& operator=(const DoubleCheckedSingleton&) = delete;
public:
static DoubleCheckedSingleton* getInstance() {
DoubleCheckedSingleton* tmp = instance.load(std::memory_order_acquire);
if (tmp == nullptr) { // 第一次检查
std::lock_guard<std::mutex> lock(mtx);
tmp = instance.load(std::memory_order_relaxed);
if (tmp == nullptr) { // 第二次检查
tmp = new DoubleCheckedSingleton();
instance.store(tmp, std::memory_order_release);
}
}
return tmp;
}
};
std::atomic<DoubleCheckedSingleton*> DoubleCheckedSingleton::instance{nullptr};
std::mutex DoubleCheckedSingleton::mtx;
4. Meyers' Singleton(推荐)
cpp
class MeyersSingleton {
private:
MeyersSingleton() {}
MeyersSingleton(const MeyersSingleton&) = delete;
MeyersSingleton& operator=(const MeyersSingleton&) = delete;
public:
static MeyersSingleton& getInstance() {
static MeyersSingleton instance; // C++11保证线程安全
return instance;
}
void businessMethod() {
// 业务方法
}
};
优点:
- 线程安全(C++11标准保证)
- 延迟初始化
- 自动销毁
- 代码简洁
现代 C++ 单例模式实现
1. 模板化单例
cpp
#include <memory>
#include <mutex>
template<typename T>
class Singleton {
protected:
Singleton() = default;
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
public:
static T& getInstance() {
static T instance;
return instance;
}
};
// 使用方式
class ConfigManager : public Singleton<ConfigManager> {
friend class Singleton<ConfigManager>;
private:
ConfigManager() = default;
public:
void loadConfig(const std::string& path) {
// 加载配置
}
std::string getValue(const std::string& key) {
return ""; // 返回配置值
}
};
2. 带参数的单例
cpp
template<typename T>
class ParametrizedSingleton {
protected:
ParametrizedSingleton() = default;
ParametrizedSingleton(const ParametrizedSingleton&) = delete;
ParametrizedSingleton& operator=(const ParametrizedSingleton&) = delete;
public:
template<typename... Args>
static T& getInstance(Args&&... args) {
static T instance(std::forward<Args>(args)...);
return instance;
}
};
class DatabaseConnection : public ParametrizedSingleton<DatabaseConnection> {
friend class ParametrizedSingleton<DatabaseConnection>;
private:
std::string connectionString_;
// 私有构造函数,接受参数
DatabaseConnection(const std::string& connStr) : connectionString_(connStr) {}
public:
void connect() {
// 连接数据库
}
};
// 使用
auto& db = DatabaseConnection::getInstance("server=localhost;database=test");
实际应用示例
1. 日志系统单例
cpp
#include <fstream>
#include <mutex>
#include <string>
#include <chrono>
#include <iomanip>
class Logger {
private:
std::ofstream logFile;
std::mutex logMutex;
Logger() {
logFile.open("application.log", std::ios::app);
if (!logFile.is_open()) {
throw std::runtime_error("无法打开日志文件");
}
}
~Logger() {
if (logFile.is_open()) {
logFile.close();
}
}
Logger(const Logger&) = delete;
Logger& operator=(const Logger&) = delete;
public:
static Logger& getInstance() {
static Logger instance;
return instance;
}
void log(const std::string& message, const std::string& level = "INFO") {
std::lock_guard<std::mutex> lock(logMutex);
auto now = std::chrono::system_clock::now();
auto time = std::chrono::system_clock::to_time_t(now);
logFile << "[" << std::put_time(std::localtime(&time), "%Y-%m-%d %H:%M:%S") << "] "
<< "[" << level << "] " << message << std::endl;
}
void info(const std::string& message) {
log(message, "INFO");
}
void error(const std::string& message) {
log(message, "ERROR");
}
void warn(const std::string& message) {
log(message, "WARN");
}
};
// 使用示例
void someFunction() {
Logger::getInstance().info("函数开始执行");
// ... 业务逻辑
Logger::getInstance().info("函数执行完成");
}
2. 配置管理器单例
cpp
#include <unordered_map>
#include <string>
#include <fstream>
#include <sstream>
class ConfigManager {
private:
std::unordered_map<std::string, std::string> config_;
bool loaded_ = false;
ConfigManager() = default;
ConfigManager(const ConfigManager&) = delete;
ConfigManager& operator=(const ConfigManager&) = delete;
public:
static ConfigManager& getInstance() {
static ConfigManager instance;
return instance;
}
bool loadFromFile(const std::string& filename) {
std::ifstream file(filename);
if (!file.is_open()) {
return false;
}
std::string line;
while (std::getline(file, line)) {
// 跳过空行和注释
if (line.empty() || line[0] == '#') continue;
size_t pos = line.find('=');
if (pos != std::string::npos) {
std::string key = line.substr(0, pos);
std::string value = line.substr(pos + 1);
// 去除前后空白
key.erase(0, key.find_first_not_of(" \t"));
key.erase(key.find_last_not_of(" \t") + 1);
value.erase(0, value.find_first_not_of(" \t"));
value.erase(value.find_last_not_of(" \t") + 1);
config_[key] = value;
}
}
loaded_ = true;
return true;
}
std::string getString(const std::string& key, const std::string& defaultValue = "") const {
auto it = config_.find(key);
return it != config_.end() ? it->second : defaultValue;
}
int getInt(const std::string& key, int defaultValue = 0) const {
auto it = config_.find(key);
if (it != config_.end()) {
try {
return std::stoi(it->second);
} catch (...) {
return defaultValue;
}
}
return defaultValue;
}
double getDouble(const std::string& key, double defaultValue = 0.0) const {
auto it = config_.find(key);
if (it != config_.end()) {
try {
return std::stod(it->second);
} catch (...) {
return defaultValue;
}
}
return defaultValue;
}
bool getBool(const std::string& key, bool defaultValue = false) const {
auto it = config_.find(key);
if (it != config_.end()) {
std::string value = it->second;
std::transform(value.begin(), value.end(), value.begin(), ::tolower);
return value == "true" || value == "1" || value == "yes";
}
return defaultValue;
}
void set(const std::string& key, const std::string& value) {
config_[key] = value;
}
bool isLoaded() const {
return loaded_;
}
};
// 使用示例
void initializeApplication() {
auto& config = ConfigManager::getInstance();
if (!config.loadFromFile("config.ini")) {
throw std::runtime_error("无法加载配置文件");
}
std::string host = config.getString("database.host", "localhost");
int port = config.getInt("database.port", 3306);
bool debug = config.getBool("app.debug", false);
}
3. 线程池单例
cpp
#include <vector>
#include <queue>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <functional>
#include <future>
class ThreadPool {
private:
std::vector<std::thread> workers;
std::queue<std::function<void()>> tasks;
std::mutex queueMutex;
std::condition_variable condition;
bool stop = false;
ThreadPool(size_t threads = std::thread::hardware_concurrency()) {
for (size_t i = 0; i < threads; ++i) {
workers.emplace_back([this] {
for (;;) {
std::function<void()> task;
{
std::unique_lock<std::mutex> lock(this->queueMutex);
this->condition.wait(lock, [this] {
return this->stop || !this->tasks.empty();
});
if (this->stop && this->tasks.empty()) {
return;
}
task = std::move(this->tasks.front());
this->tasks.pop();
}
task();
}
});
}
}
~ThreadPool() {
{
std::unique_lock<std::mutex> lock(queueMutex);
stop = true;
}
condition.notify_all();
for (std::thread& worker : workers) {
worker.join();
}
}
ThreadPool(const ThreadPool&) = delete;
ThreadPool& operator=(const ThreadPool&) = delete;
public:
static ThreadPool& getInstance() {
static ThreadPool instance;
return instance;
}
template<class F, class... Args>
auto enqueue(F&& f, Args&&... args)
-> std::future<typename std::invoke_result<F, Args...>::type> {
using return_type = typename std::invoke_result<F, Args...>::type;
auto task = std::make_shared<std::packaged_task<return_type()>>(
std::bind(std::forward<F>(f), std::forward<Args>(args)...)
);
std::future<return_type> res = task->get_future();
{
std::unique_lock<std::mutex> lock(queueMutex);
if (stop) {
throw std::runtime_error("线程池已停止");
}
tasks.emplace([task](){ (*task)(); });
}
condition.notify_one();
return res;
}
};
// 使用示例
void parallelComputation() {
auto& pool = ThreadPool::getInstance();
std::vector<std::future<int>> results;
for (int i = 0; i < 10; ++i) {
results.emplace_back(
pool.enqueue([i] {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
return i * i;
})
);
}
for (auto&& result : results) {
std::cout << result.get() << std::endl;
}
}
单例模式的变体
1. 多例模式(Multiton)
cpp
#include <map>
#include <memory>
#include <mutex>
template<typename Key, typename T>
class Multiton {
private:
static std::map<Key, std::shared_ptr<T>> instances;
static std::mutex mutex;
Multiton() = default;
Multiton(const Multiton&) = delete;
Multiton& operator=(const Multiton&) = delete;
public:
template<typename... Args>
static std::shared_ptr<T> getInstance(const Key& key, Args&&... args) {
std::lock_guard<std::mutex> lock(mutex);
auto it = instances.find(key);
if (it == instances.end()) {
auto instance = std::make_shared<T>(std::forward<Args>(args)...);
instances[key] = instance;
return instance;
}
return it->second;
}
};
template<typename Key, typename T>
std::map<Key, std::shared_ptr<T>> Multiton<Key, T>::instances;
template<typename Key, typename T>
std::mutex Multiton<Key, T>::mutex;
// 使用示例
class DatabaseConnection {
private:
std::string connectionString_;
public:
DatabaseConnection(const std::string& connStr) : connectionString_(connStr) {}
void connect() {
// 连接逻辑
}
};
void multitonExample() {
auto db1 = Multiton<std::string, DatabaseConnection>::getInstance(
"primary", "server=primary;database=main");
auto db2 = Multiton<std::string, DatabaseConnection>::getInstance(
"secondary", "server=secondary;database=backup");
db1->connect();
db2->connect();
}
单例模式的优缺点
优点
- 严格控制实例数量:确保只有一个实例存在
- 全局访问点:方便在程序的任何地方访问
- 延迟初始化:只有在第一次使用时才创建实例
- 减少资源消耗:避免重复创建相似对象
缺点
- 违反单一职责原则:同时负责创建实例和业务逻辑
- 隐藏依赖关系:使类之间的依赖关系不明显
- 难以测试:全局状态使得单元测试困难
- 线程安全问题:需要额外处理多线程环境
- 可能产生隐藏耦合:代码过度依赖单例
最佳实践
1. 使用场景
- 需要严格控制资源使用的场景(如配置管理、日志系统)
- 频繁使用的重量级对象(如数据库连接池)
- 需要全局访问点的工具类
2. 避免使用场景
- 可以用参数传递的对象
- 需要测试的代码
- 可能需要在未来扩展为多实例的类
3. 现代 C++ 建议
cpp
// 推荐使用 Meyers' Singleton
class ModernSingleton {
private:
ModernSingleton() = default;
public:
static ModernSingleton& getInstance() {
static ModernSingleton instance;
return instance;
}
// 明确删除拷贝操作
ModernSingleton(const ModernSingleton&) = delete;
ModernSingleton& operator=(const ModernSingleton&) = delete;
// 可以添加移动语义(虽然单例通常不需要移动)
ModernSingleton(ModernSingleton&&) = delete;
ModernSingleton& operator=(ModernSingleton&&) = delete;
};
总结
单例模式在特定场景下非常有用,但应该谨慎使用。在现代 C++ 中,推荐使用 Meyers' Singleton 实现,因为它简洁、线程安全且自动管理生命周期。
关键要点:
- 优先考虑依赖注入而不是单例
- 如果需要单例,使用静态局部变量实现
- 明确删除拷贝和赋值操作
- 考虑线程安全性
- 避免在单例中管理复杂的资源生命周期
单例模式是一个强大的工具,但像所有全局状态一样,应该在有充分理由时才使用。