在C++中,单例模式是一种常用的软件设计模式,它保证一个类只有一个实例,并提供一个全局访问点来获取这个实例。这种模式对于管理资源、控制对共享资源的访问或者创建一些中心控制类非常有用例如相关配置管理器、日志记录器、数据库连接池等。
通常在需要使用该类的.cpp文件引入该单例类所在的头文件,并用全局访问点访问就行了
GetInstance()方法:这个静态成员函数检查_instance是否已经被初始化,如果没有,它创建一个新的单例实例。然后返回该实例的引用。
很明显只有第一次请求时会创造并初始化单例实例,另外记得在类外定义类内声明的静态成员变量
#include<iostream>
class Singleton {
public:
static Singleton& GetInstance() {
if (!_instance) {
_instance = new Singleton();
}
return *_instance;
}
void DoSomething() {
std::cout << "Doing something useful.\n";
}
void AddCount()
{
count += 1;
}
int GetCount()
{
return count;
}
private:
Singleton() {} // 私有构造函数
~Singleton() {} // 私有析构函数
Singleton(const Singleton&) = delete; // 删除拷贝构造函数
Singleton& operator=(const Singleton&) = delete; // 删除赋值运算符
static Singleton* _instance;
int count = 0;
};
//类外定义
Singleton* Singleton::_instance = nullptr;
int main() {
Singleton& s = Singleton::GetInstance();//全局访问点
s.DoSomething();
std::cout << Singleton::GetInstance().GetCount() << std::endl;
return 0;
}
当多线程时,为什么需要额外的同步机制?
这是因为不加锁的话,多个线程可能在第一次初始化时创造出多个单例对象造成线程安全问题
通常可以使用锁或者call_once等等来同步
以call_once为例
#include <iostream>
#include <mutex>
#include <thread>
class Singleton {
public:
static Singleton& GetInstance() {
std::call_once(_onceFlag, &Singleton::initInstance);
return *_instance;
}
void DoSomething() {
std::cout << "Doing something useful.\n";
}
void Add()
{
count += 1;
}
int GetCount()
{
return count;
}
private:
Singleton() {} // 私有构造函数
~Singleton() {} // 私有析构函数
Singleton(const Singleton&) = delete; // 删除拷贝构造函数
Singleton& operator=(const Singleton&) = delete; // 删除赋值运算符
static Singleton* _instance;
static std::once_flag _onceFlag;
int count = 0;
static void initInstance() { // 静态成员函数
_instance = new Singleton();
}
};
// 类外定义
Singleton* Singleton::_instance = nullptr;
std::once_flag Singleton::_onceFlag;
int main() {
Singleton& s = Singleton::GetInstance(); // 全局访问点
s.DoSomething();
s.Add();
std::cout << "count:" <<Singleton::GetInstance().GetCount() <<std::endl;
return 0;
}
这样,无论有多少个线程试图获取 Singleton 的实例,只会有一个线程创建该实例,其他线程将等待直到 _instance 被初始化完成。
#include <algorithm>
#include <vector>
#include <string>
#include <iostream>
#include <thread>
#include <chrono>
#include <mutex>
class Singleton {
public:
static Singleton& GetInstance() {
std::call_once(_onceFlag, &Singleton::initInstance);
return *_instance;
}
void DoSomething() {
std::cout << "Doing something useful.\n";
}
void Add() {
std::lock_guard<std::mutex> lock(_mutex); // 使用锁保护对count的访问
count += 1;
}
int GetCount() const {
return count;
}
private:
Singleton() {} // 私有构造函数
~Singleton() {} // 私有析构函数
Singleton(const Singleton&) = delete; // 删除拷贝构造函数
Singleton& operator=(const Singleton&) = delete; // 删除赋值运算符
static Singleton* _instance;
static std::once_flag _onceFlag;
mutable std::mutex _mutex; // 用于同步对count的访问
int count = 0;
static void initInstance() { // 静态成员函数
_instance = new Singleton();
}
};
// 类外定义
Singleton* Singleton::_instance = nullptr;
std::once_flag Singleton::_onceFlag;
void incrementCount(Singleton& singleton, int times) {
for (int i = 0; i < times; ++i) {
singleton.Add();
}
}
int main()
{
Singleton& s = Singleton::GetInstance(); // 全局访问点
s.DoSomething();
// 创建两个线程来增加count
std::thread t1(incrementCount, std::ref(s), 5000);
std::thread t2(incrementCount, std::ref(s), 5000);
// 等待线程完成
t1.join();
t2.join();
std::cout << "count: " << s.GetCount() << std::endl;
return 0;
}
运行截图
与懒汉模式的主要区别体现在全局访问点函数以及无需在类内声明静态成员变量
#include <iostream>
class Singleton
{
public:
static Singleton& GetInstance()
{
static Singleton instance; // 静态局部变量
return instance;
}
void DoSomething()
{
std::cout << "Doing something useful.\n";
}
private:
Singleton() {} // 私有构造函数
~Singleton() {} // 私有析构函数
Singleton(const Singleton&) = delete; // 删除拷贝构造函数
Singleton& operator=(const Singleton&) = delete; // 删除赋值运算符
};
int main() {
Singleton& s = Singleton::GetInstance();//全局访问点
s.DoSomething();
return 0;
}
为控制变量
以下均基于饿汉模式下的单例模式讲解
static Singleton& GetInstance()
{
static Singleton instance; // 静态局部变量
return instance;
}
static Singleton* GetInstance()
{
static Singleton instance; // 静态局部变量
return &instance;
}
nullptr
。选择懒汉模式还是饿汉模式取决于具体需求。如果希望节省资源并且能够接受额外的同步开销,则选择懒汉模式;如果希望在程序启动时就准备好所有资源,则选择饿汉模式。
至于返回引用还是指针的选择,通常返回引用更为推荐,因为它更安全且避免了不必要的开销。但有时,返回指针会更灵活。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。