我第一次接触单例模式是在开发一个游戏引擎的日志系统时。当时需要确保全局只有一个日志管理器实例,但不同模块的开发人员都在各自创建Logger对象,导致日志文件被重复打开、写入冲突。这就是典型的需要单例模式的场景——当你需要确保一个类只有一个实例,并提供一个全局访问点时。
单例模式属于创建型设计模式,它通过控制实例化过程来限制类的实例数量。想象一下公司里的打印机服务:如果每个员工都能随意创建新的打印机实例,不仅浪费资源,还会导致打印任务混乱。正确的做法是让所有员工共享同一个打印机服务实例,这就是单例模式的现实映射。
在C++中实现单例有几个关键特征:
让我们从一个最简单的实现开始:
cpp复制class Singleton {
public:
static Singleton& getInstance() {
static Singleton instance;
return instance;
}
void doSomething() { /* 功能实现 */ }
private:
Singleton() = default;
~Singleton() = default;
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
};
这个实现有几个精妙之处:
注意:在C++11之前,这种实现方式不是线程安全的。如果你还在用老版本编译器,需要额外加锁。
对于需要支持C++11之前标准的项目,我们需要手动实现线程安全:
cpp复制class LegacySingleton {
public:
static LegacySingleton& getInstance() {
if (!instance) { // 第一次检查
std::lock_guard<std::mutex> lock(mutex);
if (!instance) { // 第二次检查
instance = new LegacySingleton();
}
}
return *instance;
}
private:
static LegacySingleton* instance;
static std::mutex mutex;
// ...其他私有成员同上
};
这就是著名的"双重检查锁定"模式。第一次检查避免每次调用都加锁,第二次检查确保在锁内只创建一次实例。不过在现代C++中,这种实现已经过时了,因为:
单例对象的销毁时机是个值得深入讨论的话题。在前面的例子中,我们依赖程序的正常退出来自动销毁单例。但在某些情况下,这可能导致问题:
解决方案之一是使用智能指针:
cpp复制class SmartSingleton {
public:
static std::shared_ptr<SmartSingleton> getInstance() {
static std::weak_ptr<SmartSingleton> weakInstance;
static std::mutex mutex;
std::lock_guard<std::mutex> lock(mutex);
auto instance = weakInstance.lock();
if (!instance) {
instance = std::make_shared<SmartSingleton>();
weakInstance = instance;
}
return instance;
}
~SmartSingleton() {
// 显式清理资源
}
private:
SmartSingleton() = default;
};
这种实现允许在最后一个使用者释放引用后自动销毁单例,同时保证线程安全。weak_ptr避免了循环引用问题。
当项目中需要多个单例类时,可以使用模板避免重复代码:
cpp复制template<typename T>
class SingletonTemplate {
public:
static T& getInstance() {
static T instance;
return instance;
}
protected:
SingletonTemplate() = default;
virtual ~SingletonTemplate() = default;
};
class Logger : public SingletonTemplate<Logger> {
friend class SingletonTemplate<Logger>;
public:
void log(const std::string& message) {
// 日志实现
}
private:
Logger() = default;
};
这种实现有几个优点:
在某些场景下,单例需要根据运行环境表现出不同行为。比如在测试环境和生产环境使用不同的配置:
cpp复制class EnvironmentAwareSingleton {
public:
static EnvironmentAwareSingleton& getInstance() {
static EnvironmentAwareSingleton instance(detectEnvironment());
return instance;
}
void operation() {
if (isTestEnv) {
// 测试环境行为
} else {
// 生产环境行为
}
}
private:
bool isTestEnv;
explicit EnvironmentAwareSingleton(bool isTest)
: isTestEnv(isTest) {}
static bool detectEnvironment() {
// 环境检测逻辑
return /* 测试环境返回true */;
}
};
有时候我们需要有限数量的实例,而不是严格单一实例。这种变体称为多例模式:
cpp复制class Multiton {
public:
enum class Key { PRIMARY, SECONDARY, FALLBACK };
static Multiton& getInstance(Key key) {
static std::map<Key, Multiton> instances {
{Key::PRIMARY, Multiton(/* 参数 */)},
{Key::SECONDARY, Multiton(/* 参数 */)},
{Key::FALLBACK, Multiton(/* 参数 */)}
};
return instances.at(key);
}
private:
explicit Multiton(/* 参数 */) {
// 初始化
}
};
这种模式常用于连接池、线程池等场景,其中每个实例可能有不同的配置或状态。
当单例依赖其他静态对象时,可能会遇到初始化顺序问题:
cpp复制// 在某个cpp文件中
static auto& config = ConfigManager::getInstance(); // 可能尚未初始化
// 解决方案:使用函数包装
ConfigManager& getConfig() {
static auto& instance = ConfigManager::getInstance();
return instance;
}
经验法则:永远不要在静态变量初始化中直接或间接调用可能尚未初始化的单例。
即使单例初始化是线程安全的,成员方法的调用仍可能需要同步:
cpp复制class ThreadSafeSingleton {
public:
static ThreadSafeSingleton& getInstance() {
static ThreadSafeSingleton instance;
return instance;
}
void unsafeOperation() {
std::lock_guard<std::mutex> lock(mutex);
// 临界区操作
}
private:
std::mutex mutex;
};
单例模式常被认为是"测试不友好"的,因为它引入了全局状态。解决方案包括:
cpp复制class TestableSingleton {
public:
static TestableSingleton& getInstance() {
if (testInstance) return *testInstance;
static TestableSingleton instance;
return instance;
}
static void setTestInstance(TestableSingleton* instance) {
testInstance = instance;
}
protected:
static TestableSingleton* testInstance;
};
现代C++项目越来越倾向于使用依赖注入(DI)框架来管理单例生命周期:
cpp复制// 使用Boost.DI示例
auto injector = boost::di::make_injector(
boost::di::bind<ILogger>.to<Logger>().in(boost::di::singleton)
);
// 获取单例实例
auto& logger = injector.create<ILogger&>();
DI框架的优势:
C++17引入的并发数据结构可以与单例模式结合:
cpp复制class Statistics {
public:
static Statistics& getInstance() {
static Statistics instance;
return instance;
}
void recordEvent(std::string_view event) {
events.push_back(std::string(event));
}
private:
std::pmr::synchronized_pool_resource pool;
std::pmr::vector<std::pmr::string> events{&pool};
};
在协程环境中使用单例需要注意:
cpp复制class CoroutineSingleton {
public:
static CoroutineSingleton& getInstance() {
static CoroutineSingleton instance;
return instance;
}
cppcoro::task<> asyncOperation() {
co_await someAsyncWork();
}
};
关键点:
结合工厂模式创建不同类型的单例:
cpp复制class AbstractService {
public:
virtual ~AbstractService() = default;
virtual void execute() = 0;
};
class ServiceFactory {
public:
static AbstractService& getService() {
static std::unique_ptr<AbstractService> instance = createService();
return *instance;
}
private:
static std::unique_ptr<AbstractService> createService() {
// 根据配置返回不同实现
return std::make_unique<ConcreteService>();
}
};
实现全局事件总线:
cpp复制class EventBus {
public:
static EventBus& getInstance() {
static EventBus instance;
return instance;
}
void subscribe(std::function<void(Event)> handler) {
std::lock_guard lock(mutex);
handlers.push_back(std::move(handler));
}
void publish(Event event) {
std::lock_guard lock(mutex);
for (auto& handler : handlers) {
handler(event);
}
}
private:
std::vector<std::function<void(Event)>> handlers;
std::mutex mutex;
};
运行时配置单例行为:
cpp复制class ConfigurableSingleton {
public:
static ConfigurableSingleton& getInstance() {
static ConfigurableSingleton instance;
return instance;
}
void setStrategy(std::unique_ptr<Strategy> strategy) {
this->strategy = std::move(strategy);
}
void execute() {
strategy->apply();
}
private:
std::unique_ptr<Strategy> strategy;
};
单例的内存布局会影响性能:
cpp复制class AlignedSingleton {
public:
static AlignedSingleton& getInstance() { /*...*/ }
void processData() {
// 使用对齐内存操作
}
private:
alignas(64) std::array<float, 1024> simdData;
// 其他成员...
};
根据访问模式优化单例设计:
cpp复制class HighPerformanceSingleton {
public:
static HighPerformanceSingleton& getInstance() { /*...*/ }
void batchUpdate(std::span<const Update> updates) {
std::lock_guard lock(mutex);
for (const auto& update : updates) {
applyUpdate(update);
}
}
private:
std::mutex mutex;
// 数据成员...
};
根据场景选择加载策略:
cpp复制class PreloadedSingleton {
public:
static void initialize() { getInstance(); }
static PreloadedSingleton& getInstance() {
static PreloadedSingleton instance;
return instance;
}
private:
PreloadedSingleton() {
// 耗时的初始化操作
}
};
// 在main()早期调用
PreloadedSingleton::initialize();
在Windows DLL中使用单例需要特别注意:
cpp复制// 在DLL头文件中
#ifdef SINGLETON_EXPORTS
#define SINGLETON_API __declspec(dllexport)
#else
#define SINGLETON_API __declspec(dllimport)
#endif
extern "C" SINGLETON_API SingletonInterface& getSingletonInstance();
当单例需要集成系统API时:
cpp复制class SystemIntegrationSingleton {
public:
static SystemIntegrationSingleton& getInstance() {
static SystemIntegrationSingleton instance;
return instance;
}
void systemCall() {
#ifdef _WIN32
// Windows特定实现
#else
// POSIX实现
#endif
}
private:
// 平台特定成员...
};
在资源受限环境中:
cpp复制class EmbeddedSingleton {
public:
static EmbeddedSingleton& getInstance() {
static EmbeddedSingleton instance;
return instance;
}
private:
// 使用静态数组而非动态容器
std::array<uint8_t, 1024> fixedBuffer;
};
通过显式传递上下文对象:
cpp复制class ApplicationContext {
public:
Logger& getLogger() { return logger; }
Config& getConfig() { return config; }
private:
Logger logger;
Config config;
};
// 使用时显式传递
void processData(ApplicationContext& context) {
context.getLogger().log("Processing");
}
服务定位器作为单例的替代:
cpp复制class ServiceLocator {
public:
static void provide(Logger* service) {
loggerService = service;
}
static Logger& getLogger() {
assert(loggerService && "Service not provided");
return *loggerService;
}
private:
static Logger* loggerService;
};
// 初始化时设置服务
ServiceLocator::provide(&loggerInstance);
现代C++依赖注入方案:
cpp复制struct Services {
std::shared_ptr<ILogger> logger;
std::shared_ptr<IDatabase> db;
};
class Consumer {
public:
explicit Consumer(Services services)
: services(std::move(services)) {}
void operation() {
services.logger->log("Operation started");
}
private:
Services services;
};
经过多年实践,我总结了以下单例模式使用准则:
在最近的一个分布式系统中,我们采用了这样的单例实践:
这种分层策略既保证了必要的全局访问点,又保持了系统的可测试性和灵活性。