1 什么是单例模式
单例模式 是一种创建型设计模式,确保一个类在整个程序生命周期中只有一个实例,并提供一个全局访问点。
核心要求:
- 类不能被外部随意创建(禁止 public 构造函数或限制实例数量)。
- 不能被复制或移动。
- 提供一个全局访问接口(如
getInstance()
)。- 实例的生命周期通常伴随整个程序。
2 模版单例的实现
2.1 单例模版GSingleton<T>
使用场景:项目中需要大量单例,且希望统一管理时,使用单例模版会提高代码复用。单例模版类本身不能直接使用,而是作为基类被继承。需要实现单例的类只需要继承GSingleton即可。
注意:模版基类GSingleton的构造函数和析构函数必须是protected修饰,不能用private,因为派生类在构造时或者析构时可以调用。而派生类T的构造函数和析构函数可以由private修饰。
template<typename T>
class GSingleton
{
protected:
GSingleton() = default;
~GSingleton() = default; // 非虚,除非你真的需要
// 禁止拷贝和移动
GSingleton(const GSingleton&) = delete;
GSingleton& operator=(const GSingleton&) = delete;
GSingleton(GSingleton&&) = delete;
GSingleton& operator=(GSingleton&&) = delete;
public:
static T& getInstance() {
static T instance;
return instance;
}
};
- 模版类详细介绍
- protect:修饰的方法或者成员变量只能被本类、派生类以及友元访问。
- Gsingleton<T>是基类,必须是protected,这样派生类在构造时才可以调用父类的构造函数。
- GSingleton = default:表示显式要求编译器生成一个默认构造函数(无参构造函数)
- ~GSingleton() = default:表示让编译器生成默认析构函数。
- GSingleton(const GSingleton&) = delete:这是对拷贝构造函数的声明,禁止拷贝。
const GSingleton&:表示对另一个GSingleton对象的常量引用。
delete表示这个函数被显示删除,不能使用。Access a; Access b(a); // ❌ 错误!调用拷贝构造函数 Access c = a; // ❌ 错误!也是拷贝构造(赋值语法,实际是构造)
- GSingleton& operator=(const GSingleton&) = delete:这是对拷贝赋值运算符的声明,禁止拷贝。
operator=:表示赋值操作符的重载。Access a; Access b; b = a; // ❌ 错误!调用拷贝赋值
- GSingleton(GSingleton&&) = delete:这是移动构造函数的声明,禁止移动构造。
GSingleton&&:是一个右值引用,表示临时对象或者可移动的对象。
移动不是拷贝,而是资源转移,将一个临时对象所拥有的资源转移给另一个对象,而原对象不再拥有该资源。
移动避免了不必要的内存分配和数据赋值,因此非常高效。Access a; Access b(std::move(a)); // ❌ 错误!试图移动构造(即使 NRVO 优化,语义上也不允许)
- GSingleton& operator=(GSingleton&&) = delete:这是移动赋值运算符,禁止将一个临时对象移动赋值给已有对象。
Access a; Access b; b = std::move(a); // ❌ 错误!禁止移动赋值
✅ 总结
代码 | 禁止的操作 |
---|---|
Access(const Access&) = delete; |
Access b(a); 或 Access b = a; |
Access& operator=(const Access&) = delete; |
b = a; |
Access(Access&&) = delete; |
Access b(std::move(a)); |
Access& operator=(Access&&) = delete; |
b = std::move(a); |
2.2 使用单例模版定义派生类
class GLoggerImpl : public GSingleton<GLoggerImpl>
{
private:
GLoggerImpl();
friend class GSingleton<GLoggerImpl>;
public:
~GLoggerImpl();
void WriteLog(int logLevel, const char* file, int line, const char* function, const char* format, va_list args);
}
注意:在声明GloggerImpl的类时使用了friend class GSingleton<GLoggerImpl>,表示声明让模版类成为友元。
原因:在调用GLoggerImpl::getInstance()时,
// template<typename T> class GSingleton
static T& getInstance() {
static T instance; // ← 这里要构造 T,即 GLoggerImpl
return instance;
}
static T instance会调用GLoggerImpl的构造函数,由于GLoggerImpl()是private或者protected,而GSingleton<GLoggerImpl>模版类不是GLoggerImpl类的成员变量,默认无法访问GLoggerImpl的构造函数,因此要声明friend来显示授权。
友元:是C++的一个关键字,它允许一个类、函数或者模版访问另一个类的private和protected成员,即使它不是那个类的成员函数或者派生类。
- friend可以修饰函数,称为友元函数,可以在一个类中声明友元函数,这样该函数可以访问该类的私有成员变量或者成员函数。
- friend可以修饰类,成为友元类,可以访问所在类中的private或者protect的成员,友元关系是单向的。
- friend可以修饰一个模版类或者模版函数,成为友元模版。例如GloggerImpl中friend修饰的模版。
2.3 单例类的使用
由于GLoggerImpl继承了GSingleton的模版基类,因此继承了父类静态的getInstance()方法。所以通过GLoggerImpl::getInstance()可以获取该类唯一的单例对象。
void LogD(const char* file, int line, const char* function, const char* format, ...) {
va_list args;
va_start(args, format);
GLoggerImpl::getInstance()->WriteLog(LEVEL_DEBUG, file, line, function, format, args);
va_end(args);
}
3 单例类的实现
class Singleton {
public:
static Singleton& getInstance() {
static Singleton instance; // C++11 起,线程安全
return instance;
}
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
Singleton(Singleton&&) = delete;
Singleton& operator=(Singleton&&) = delete;
private:
Singleton() = default;
~Singleton() = default;
};