设计模式 ------ 单例模式
在了解C++面向对象的三大特性:封装,继承,多态之后。我们创建类的时候就有了比较大的空间。但是,我们平时在创建类的时候,不是简单写个class和继承关系就完事了的。我们写的类要在一些场景下满足一些特殊的要求。
一个问题
现在有一个具体的要求,创建一个类,保证处处只有一个实体,这个要求在平常的工作中是很常见的, 配置文件管理,日志系统,数据库连接池, 线程池等。
如何实现呢?保证只有一个实体,可以考虑静态成员变量 ,我们之前在C++继承中说过,无论继承关系有多少层,只要为静态成员,全局就只有一份。所以我们可以先从这个方向入手。
cpp
//单例模式
class SingleClass
{
public:
static SingleClass* GetInstance()
{
if (instance == nullptr)
{
instance = new SingleClass();
}
return instance;
}
static SingleClass* instance;
};
//静态成员在外部初始化
SingleClass* SingleClass::instance = nullptr;
int main()
{
SingleClass* s1 = SingleClass::GetInstance();
SingleClass* s2 = SingleClass::GetInstance();
if (s1 == s2)
{
std::cout << "s1和s2为同一实体" << std::endl;
}
else
{
std::cout << "s1和s2不为同一实体" << std::endl;
}
}
![](https://i-blog.csdnimg.cn/direct/fc510ec5a11145e0a58191a42844952a.png)
这样看上去问题解决了,但是:
我们可以在类外创建对象,这不符合我们的要求,究其原因,我们把构造函数设为了公有解决这个问题将它声明为私有就可以了。
cpp
//单例模式
class SingleClass
{
public:
static SingleClass* GetInstance()
{
if (instance == nullptr)
{
instance = new SingleClass();
}
return instance;
}
private:
SingleClass(){} //构造函数为私有
static SingleClass* instance;
};
![](https://i-blog.csdnimg.cn/direct/8fc152c4d43d4e06980d0207ceace5c0.png)
这样就可以了,但是别忘了我们还有拷贝构造和赋值拷贝,这两个也可以构造出新对象,所以为了保险可以直接把他俩禁了:
cpp
//单例模式
class SingleClass
{
public:
static SingleClass* GetInstance()
{
if (instance == nullptr)
{
instance = new SingleClass();
}
return instance;
}
private:
SingleClass(){} //构造函数为私有
SingleClass(const SingleClass&) = delete; // 禁止拷贝构造
SingleClass& operator=(const SingleClass&) = delete; // 禁止赋值操作
static SingleClass* instance;
};
//静态成员在外部初始化
SingleClass* SingleClass::instance = nullptr;
这就是单例模式的雏形了。
单例模式(Singleton Pattern) 实现一个类保证处处只有一个实例。单例模式的核心思想是:
私有化构造函数:禁止外部直接创建对象。
静态方法获取实例:通过静态成员函数控制唯一实例的创建和访问。
禁止拷贝和赋值:防止通过拷贝构造函数或赋值操作生成新实例。
上面的代码还不能保证在多线程条件下是安全的的:
cpp
//单例模式
class SingleClass
{
public:
static SingleClass* GetInstance()
{
if (instance == nullptr)
{
instance = new SingleClass();
}
return instance;
}
void PrintAddress()
{
std::cout << "地址为:" << this << std::endl;
}
private:
SingleClass(){} //构造函数为私有
SingleClass(const SingleClass&) = delete; // 禁止拷贝构造
SingleClass& operator=(const SingleClass&) = delete; // 禁止赋值操作
static SingleClass* instance;
//static std::mutex mtx;
};
//静态成员在外部初始化
SingleClass* SingleClass::instance = nullptr;
//std::mutex SingleClass::mtx;
// 全局互斥锁,用于保护输出
std::mutex coutMtx;
// 线程函数
void threadFunc()
{
SingleClass* instance = SingleClass::GetInstance();
std::lock_guard<std::mutex> lock(coutMtx); // 加锁保护输出
instance->PrintAddress();
}
int main()
{
const int threadNumber = 100;
std::vector<thread> threadVT;
//创建多个线程
for (int i = 0; i < threadNumber; i++)
{
threadVT.emplace_back(threadFunc);
}
for (auto& t : threadVT)
{
t.join();
}
}
大家可以试一下,可能会有不同的地址。为了保证线程安全,我们还得加锁:
cpp
//单例模式
class SingleClass
{
public:
static SingleClass* GetInstance()
{
if (instance == nullptr)
{
std::lock_guard<std::mutex> lock(mtx); // 加锁
instance = new SingleClass();
}
return instance;
}
private:
SingleClass(){} //构造函数为私有
SingleClass(const SingleClass&) = delete; // 禁止拷贝构造
SingleClass& operator=(const SingleClass&) = delete; // 禁止赋值操作
static SingleClass* instance;
static std::mutex mtx;
};
//静态成员在外部初始化
SingleClass* SingleClass::instance = nullptr;
std::mutex SingleClass::mtx;
C++11后代写法
在 C++11 中,局部静态变量的初始化是线程安全的,因此可以简化代码
cpp
class SingleClass
{
public:
static SingleClass& GetInstance()
{
static SingleClass instance;
return instance;
}
private:
SingleClass() {};
SingleClass(const SingleClass&) = delete; // 禁止拷贝构造
SingleClass& operator=(const SingleClass&) = delete; // 禁止赋值操作
};
int main()
{
SingleClass& s1 = SingleClass::GetInstance();
SingleClass& s2 = SingleClass::GetInstance();
if (&s1 == &s2)
{
std::cout << "s1和s2同一对象" << std::endl;
}
else
{
std::cout << "s1和s2不为同一对象" << std::endl;
}
}
单例模式的两种模式
饿汉模式
饿汉模式讲究的是对象已经创建好,要用的时候直接拿就行
cpp
class SingleClass
{
public:
static SingleClass* GetInstance()
{
return instance;
}
private:
SingleClass() {}
SingleClass(const SingleClass&) = delete; // 禁止拷贝构造
SingleClass& operator=(const SingleClass&) = delete; // 禁止赋值操作
static SingleClass* instance;
};
SingleClass* SingleClass::instance = new SingleClass();
懒汉模式
懒汉模式讲究的是对象要用时才创建:
cpp
class SingleClass
{
public:
static SingleClass* GetInstance()
{
if (instance == nullptr)
{
instance = new SingleClass();
return instance;
}
}
private:
SingleClass() {}
SingleClass(const SingleClass&) = delete; // 禁止拷贝构造
SingleClass& operator=(const SingleClass&) = delete; // 禁止赋值操作
static SingleClass* instance;
};
SingleClass* SingleClass::instance = nullptr;
这个和我们最开始写的代码差不多,要改进的话还要保证线程安全。
我们可以将这种思想放到我们实际的代码中,比如我们日志系统的开发:
cpp
class LoggerManager
{
public:
static LoggerManager &getInstance()
{
// c++11之后,针对静态局部变量,编译器在编译的层面实现了线程安全
// 当静态局部变量在没有构造完成之前,其他的线程进入就会阻塞
static LoggerManager eton;
return eton;
}
void addLogger(logs::logger::ptr &logger)
{
if (hasLogger(logger->name()))
return;
std::unique_lock<std::mutex> lock(_mutex);
_loggers.insert(std::make_pair(logger->name(), logger));
}
bool hasLogger(const std::string &name)
{
std::unique_lock<std::mutex> lock(_mutex);
auto it = _loggers.find(name);
if (it == _loggers.end())
{
return false;
}
return true;
}
logs::logger::ptr getLogger(const std::string &name)
{
std::unique_lock<std::mutex> lock(_mutex);
auto it = _loggers.find(name);
if (it == _loggers.end())
{
return logger::ptr();
}
return it->second;
}
logs::logger::ptr rootLogger()
{
return _root_logger;
}
private:
LoggerManager()
{
std::unique_ptr<logs::LoggerBuilder> builder(new logs::LocalloggerBuild());
builder->buildLoggerName("root");
_root_logger = builder->build();
_loggers.insert(std::make_pair("root", _root_logger));
}
private:
std::mutex _mutex;
logs::logger::ptr _root_logger; // 默认日志器
std::unordered_map<std::string, logger::ptr> _loggers;
};