在了解C++面向对象的三大特性:封装,继承,多态之后。我们创建类的时候就有了比较大的空间。但是,我们平时在创建类的时候,不是简单写个class和继承关系就完事了的。我们写的类要在一些场景下满足一些特殊的要求。
一个问题
现在有一个具体的要求,创建一个类,保证处处只有一个实体,这个要求在平常的工作中是很常见的, 配置文件管理,日志系统,数据库连接池, 线程池等。
如何实现呢?保证只有一个实体,可以考虑静态成员变量,我们之前在C++继承中说过,无论继承关系有多少层,只要为静态成员,全局就只有一份。所以我们可以先从这个方向入手。
//单例模式
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;}}
这样看上去问题解决了,但是:
我们可以在类外创建对象,这不符合我们的要求,究其原因,我们把构造函数设为了公有解决这个问题将它声明为私有就可以了。
//单例模式
class SingleClass
{
public:static SingleClass* GetInstance(){if (instance == nullptr){instance = new SingleClass();}return instance;}
private:SingleClass(){} //构造函数为私有static SingleClass* instance;
};
这样就可以了,但是别忘了我们还有拷贝构造和赋值拷贝,这两个也可以构造出新对象,所以为了保险可以直接把他俩禁了:
//单例模式
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) 实现一个类保证处处只有一个实例。单例模式的核心思想是:
私有化构造函数:禁止外部直接创建对象。
静态方法获取实例:通过静态成员函数控制唯一实例的创建和访问。
禁止拷贝和赋值:防止通过拷贝构造函数或赋值操作生成新实例。
上面的代码还不能保证在多线程条件下是安全的的:
//单例模式
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();}
}
大家可以试一下,可能会有不同的地址。为了保证线程安全,我们还得加锁:
//单例模式
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 中,局部静态变量的初始化是线程安全的,因此可以简化代码
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;}
}
单例模式的两种模式
饿汉模式
饿汉模式讲究的是对象已经创建好,要用的时候直接拿就行
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();
懒汉模式
懒汉模式讲究的是对象要用时才创建:
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;
这个和我们最开始写的代码差不多,要改进的话还要保证线程安全。
我们可以将这种思想放到我们实际的代码中,比如我们日志系统的开发:
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;};