挑战目标
基于设计模式原则与现代C++特性,实现一个线程安全的单例模板类。你需要掌握单例模式的核心思想,理解线程安全的重要性,并能够通过模板技术实现可复用的单例基类。挑战包含两种经典实现方式:Meyers单例(现代C++推荐)和懒汉式单例(使用std::call_once)。
具体要求
1. 单例模式基本要求
- 唯一实例保证:确保在整个程序生命周期中,特定类型只有一个实例存在
- 全局访问点:提供静态方法获取单例实例
- 构造控制:私有化构造函数、拷贝构造函数、拷贝赋值运算符
- 线程安全:保证在多线程环境下实例创建的唯一性
2. Meyers单例实现
- 使用函数局部静态变量实现单例
- 利用C++11标准保证的线程安全初始化
- 简单高效,现代C++推荐方式
- 模板化设计,支持任意类型作为单例
3. 懒汉式单例实现
- 使用静态指针延迟初始化
- 使用std::once_flag和std::call_once保证线程安全
- 支持手动释放单例资源(可选)
- 提供对比分析两种实现方式的优缺点
4. 模板类设计
- 创建可复用的Singleton模板基类
- 派生类通过继承获得单例特性
- 支持protected构造函数,确保只能通过单例接口创建
- 使用CRTP(Curiously Recurring Template Pattern)模式
5. 使用示例与扩展
- 实现具体单例类(如Logger、ConfigManager)
- 添加业务方法验证单例功能
- 考虑异常安全与资源管理
难度分级
基础版(必须完成)
实现Meyers单例模板类:
- 模板类Singleton<T>,使用局部静态变量
- 提供静态方法get_instance()返回单例引用
- 禁用拷贝构造和赋值操作(= delete)
- protected默认构造函数和析构函数
创建具体单例类:
- Logger类继承Singleton<Logger>
- 实现log(const std::string&)方法
- 验证单例唯一性
基本测试用例:
- 验证同一单例类的多个引用指向同一实例
- 验证日志功能正常工作
进阶版(挑战提升)
实现懒汉式单例变体:
- 模板类LazySingleton<T>,使用std::unique_ptr和std::once_flag
- 支持手动destroy_instance()方法(可选)
- 对比两种实现的性能与使用场景
线程安全验证:
- 设计多线程测试,验证并发访问时实例创建的唯一性
- 使用std::thread模拟高并发场景
异常安全增强:
- 考虑构造函数可能抛出异常的情况
- 保证异常发生时资源正确清理
高级模板特性:
- 支持模板参数传递构造参数
- 实现可变参数模板版本的create_instance()
性能分析与优化:
- 对比两种实现的内存占用与初始化开销
- 考虑使用内存屏障或原子操作优化
参考实现要点
1. Meyers单例模板类(基础版)
template<typename T>
class Singleton {
public:
// 删除拷贝构造和赋值
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
// 获取单例实例
static T& get_instance() {
static T instance;
return instance;
}
protected:
Singleton() = default;
~Singleton() = default;
};
2. 具体单例类使用示例
class Logger : public Singleton<Logger> {
friend class Singleton<Logger>;
private:
Logger() = default; // 私有构造函数
public:
void log(const std::string& message) {
std::cout << "[LOG] " << message << std::endl;
}
void set_log_level(int level) {
log_level = level;
}
private:
int log_level = 0;
};
3. 懒汉式单例实现(进阶版)
template<typename T>
class LazySingleton {
private:
static std::unique_ptr<T> instance;
static std::once_flag init_flag;
static void create_instance() {
instance = std::make_unique<T>();
}
public:
// 删除拷贝构造和赋值
LazySingleton(const LazySingleton&) = delete;
LazySingleton& operator=(const LazySingleton&) = delete;
// 获取单例实例
static T& get_instance() {
std::call_once(init_flag, create_instance);
return *instance;
}
// 可选:手动释放单例
static void destroy_instance() {
instance.reset();
init_flag = std::once_flag(); // 重置标志允许重新创建
}
protected:
LazySingleton() = default;
~LazySingleton() = default;
};
// 静态成员定义
template<typename T>
std::unique_ptr<T> LazySingleton<T>::instance = nullptr;
template<typename T>
std::once_flag LazySingleton<T>::init_flag;
4. 使用可变参数模板的创建方法(高级)
template<typename T>
class AdvancedSingleton {
public:
template<typename… Args>
static T& get_instance(Args&&… args) {
static T instance(std::forward<Args>(args)…);
return instance;
}
// 其他成员…
};
测试用例示例
测试用例 1:单例唯一性验证
void test_singleton_uniqueness() {
Logger& logger1 = Logger::get_instance();
Logger& logger2 = Logger::get_instance();
// 验证两个引用指向同一实例
assert(&logger1 == &logger2);
// 通过一个引用修改状态,验证另一个引用看到相同状态
logger1.set_log_level(2);
std::cout << "单例唯一性测试通过!" << std::endl;
}
测试用例 2:功能正确性验证
void test_functionality() {
Logger& logger = Logger::get_instance();
// 测试日志功能
logger.log("测试消息1");
logger.log("测试消息2");
// 验证状态持久性
logger.set_log_level(3);
// 重新获取实例验证状态保持
Logger& same_logger = Logger::get_instance();
std::cout << "功能正确性测试通过!" << std::endl;
}
测试用例 3:线程安全验证(进阶)
#include <thread>
#include <vector>
#include <mutex>
std::mutex mtx;
std::vector<Logger*> instances;
void thread_func(int id) {
Logger& logger = Logger::get_instance();
std::lock_guard<std::mutex> lock(mtx);
instances.push_back(&logger);
logger.log("线程 " + std::to_string(id) + " 访问单例");
}
void test_thread_safety() {
const int num_threads = 10;
std::vector<std::thread> threads;
instances.clear();
// 创建多个线程同时访问单例
for (int i = 0; i < num_threads; ++i) {
threads.emplace_back(thread_func, i);
}
// 等待所有线程完成
for (auto& t : threads) {
t.join();
}
// 验证所有线程获取的是同一个实例
Logger* first_instance = instances[0];
for (size_t i = 1; i < instances.size(); ++i) {
assert(instances[i] == first_instance);
}
std::cout << "线程安全测试通过!共 " << num_threads
<< " 个线程访问同一实例。" << std::endl;
}
测试用例 4:懒汉式单例对比测试
void test_lazy_singleton() {
// 测试懒汉式单例
class Config : public LazySingleton<Config> {
friend class LazySingleton<Config>;
private:
Config() = default;
public:
std::string get_value(const std::string& key) {
return "value_" + key;
}
};
Config& config1 = Config::get_instance();
Config& config2 = Config::get_instance();
assert(&config1 == &config2);
assert(config1.get_value("test") == "value_test");
std::cout << "懒汉式单例测试通过!" << std::endl;
}
扩展思考题
1. 单例模式的适用场景与争议
- 单例模式在什么情况下是合适的?什么情况下应该避免使用?
- 全局状态管理有哪些替代方案?(依赖注入、服务定位器等)
- 单例模式如何影响代码的可测试性?如何设计可测试的单例?
2. C++11/14/17/20对单例实现的改进
- C++11如何保证静态局部变量的线程安全初始化?
- 使用std::call_once与Meyers单例的性能对比如何?
- C++17的inline变量对单例实现有什么影响?
- C++20的std::atomic和内存模型如何进一步优化线程安全?
3. 高级单例变体设计
- 如何实现一个带生命周期管理的单例?(允许销毁和重新创建)
- 如何设计支持多态的单例基类?
- 考虑单例的序列化与反序列化支持
- 实现一个线程局部单例(Thread-local Singleton)
4. 设计模式组合应用
- 单例工厂模式:使用单例管理工厂实例
- 单例与观察者模式结合:单例的事件通知系统
- 单例与策略模式:单例配置管理器支持多种策略
5. 性能与内存优化深入
- 单例的饿汉式初始化 vs 懒汉式初始化的权衡
- 使用双重检查锁定模式(Double-Checked Locking)的现代实现
- 单例的内存对齐与缓存友好性考虑
- 单例在多核处理器上的性能表现
验收标准
调试与优化建议
开始时间:1小时
难度评级:★★★★☆(中等偏高)
核心技能:设计模式、模板元编程、线程安全、现代C++特性
推荐资源:src/week2/singleton_factory.cpp、outputs/深度输入/Week2_Day5_单例工厂模式简报.md



