背景
首先,老生常谈一下单例模式,全局同时只允许存在一个单例对象(谨记),通过私有构造函数,提供静态的获取类实例对象接口的方式实现,分为饿汉和懒汉两种方式。饿汉:使用到的时候再通过接口去实例化(节省空间,线程不安全如多个线程同时构造此单例时,会出现多个实例);懒汉:初始化时已经创建实例,接口只用来返回(浪费内存,无线程安全问题)。但实际项目中大多场景会使用饿汉,并用锁的方法是解决线程安全问题。
但是一个类在声明为单例的时候,其自身已被限制为单例类,无法具有普通类赋值拷贝等操作,在具体项目中使得声明为单例的功能类使用起来十分不灵活(比如某些处于逻辑中间的功能类,在某种情况下上层对其有部分单例类使用的需求,但也可作为一般功能类使用),此时直接声明为单例就很不方便。
本文在工作项目的背景下设计一个单例模版本类,在编写特殊场景类时只需要声明此单例模板类为友元类,就可同时把此类当作普通类使用和部分功能单例类使用,同时使用此单例友元对象来管理类对象的生命周期,可以防止相同类对象多次析构的问题,具体见后面实例。
单例模板类实现
此单例采用饿汉的方式,同时引入C++11的std::once_flag对象来保证全局只存在一份此单例类,保证线程安全,具体实现见 Sington.hpp:
/*
* @file Sington.hpp
* @brief 单例模板类
*/
#ifndef LM_SINGTON_H_
#define LM_SINGTON_H_
#include <mutex>
template<typename T>
class CSington
{
public:
using pointer = T*;
// 禁止空构造 拷贝复制
CSington() = delete;
CSington(const CSington&) = delete;
CSington& operator=(const CSington&) = delete;
static T* instance()
{
std::call_once(m_instantiated, []() {
pInstance = new T;
});
return pInstance;
}
static void destory()
{
if (pInstance)
{
delete pInstance;
pInstance = nullptr;
}
}
private:
static T* pInstance;
static std::once_flag m_instantiated;
};
template<typename T>
T* CSington<T>::pInstance = nullptr;
template<typename T>
std::once_flag CSington<T>::m_instantiated;
#endif // !LM_SINGTON_H_
饿汉单例模式的实现此处不再啰嗦,重点说说C++11的std::once_flag和std::call_once。
C++11的std::once_flag和std::call_once的组合使用可达到当前对象只需要初始化一次,可完美处理饿汉模式的线程不安全问题。
首先我们来看一下他两在std中的声明:
struct once_flag
{
constexpr once_flag() noexcept;
once_flag(const once_flag&) = delete;
once_flag& operator=(const once_flag&) = delete;
};
template<class Callable, class ...Args>
void call_once(once_flag& flag, Callable&& func, Args&&... args);
} // std
可以看到once_flag是不允许修改的,拷贝构造函数和operator=函数都声明为delete,这样防止程序员乱用。另外,call_once也是很简单的,只要传进一个once_flag,回调函数,和参数列表就可以了。实际上once_flag相当于一个锁,使用它的线程都会在上面等待,只有一个线程允许执行。如果该线程抛出异常,那么从等待中的线程中选择一个,重复上面的流程。具体的细节像我这种菜鸟目前还不是很能懂,推荐大家去看大佬的博客。
单例模板使用样例
在定义的功能类时,只需要把套用此模板的单例类申明为友元类,包含此功能类模板的单例类就可以当作单例类来使用,非常方便,详见 MainApp.h
/*
* @file: MainApp.h
* @brief:.主程序驱动类
*/
#ifndef LM_MAINAPP_H_
#define LM_MAINAPP_H_
#include <iostream>
#include <string>
#include <memory>
#include "MzmProjectImp.h"
#include "Sington.hpp"
class MainApp
{
friend class CSington<MainApp>; // 关键
public:
MainApp(const MainApp&) = delete;
MainApp& operator=(const MainApp&) = delete;
~MainApp();
// 初始化运行环境,加载插件等
static bool init();
// 反初始化
static void uninit();
private:
MainApp();
void appStartLog();
private:
std::string m_appPath;
std::string m_dateTime;
};
using CMainApp = CSington<MainApp>;
#endif // !LM_MAINAPP_H_
使用代码:
#include "MainApp.h"
#include <QApplication>
#include <QDebug>
int main(int argc, char* argv[])
{
if (argc > 2 && "-cmd" != std::string(argv[1]))
{
qDebug() << "Invalid parameter";
return 0;
}
bool withUi = (1 == argc);
QApplication app(argc, argv);
if (!withUi)
{
return 0;
}
if (!MainApp::init())
{
CMainApp::destory();
return 0;
}
QObject::connect(&app, &QCoreApplication::aboutToQuit, []() {
CMainApp::destory();
});
return app.exec();
}
上面例子中CMainApp->instance()在init()函数中实现,当然对于CMainApp->instance()实例对象的使用例子中并没有提现,但这是我实战项目中截取出的代码,保证是没有问题,请童鞋们放心使用并及时指正问题。