使用CRTP实现单例
什么是单例?
单例模式(Singleton Pattern)是一种常用的设计模式,目的是确保一个类只有一个实例,并且提供一个全局访问点来访问这个实例。换句话说,单例模式保证某个类在整个程序的生命周期内只有一个对象实例存在。
单例模式的关键点:
唯一实例:单例类只能有一个实例,不能创建多个实例。
全局访问:通过某个静态方法(通常是
GetInstance
)来获取唯一的实例,而不需要每次创建新的对象。私有化构造函数:为了防止外部通过
new
操作符来创建多个实例,单例类的构造函数必须私有化。线程安全(可选):如果在多线程环境下使用,需要确保实例的创建是线程安全的。
单例模式的典型应用场景:
数据库连接池:通常数据库连接池只需要一个实例来管理所有的数据库连接。
配置管理器:应用程序的配置通常只需要一个实例来读取和管理。
日志系统:日志系统往往只需要一个实例来控制日志的输出。
单例模式的优点:
全局访问:可以在任何地方访问同一个实例,不需要传递实例。
节省资源:避免了多次创建相同对象的开销,尤其是创建成本较高的对象(如数据库连接)。
控制实例数量:只会有一个实例,避免了资源的浪费。
单例模式的缺点:
全局状态:单例模式使得类的状态变成全局的,可能导致程序的可测试性和可维护性下降。
难以扩展:单例模式的设计过于固定,可能会限制其灵活性,增加了代码耦合度。
多线程问题:在多线程环境下,如果没有适当的处理,可能会出现并发问题。
列举所有例子,彻底解决有关单例写法的所有问题
懒汉式单例
只有在需要使用实例时才创建,但可能存在线程安全问题。
线程安全,内存安全
但是 C++11 后,静态局部变量在第一次调用时会被初始化,并且线程安全。
#include <iostream>
class Singleton {
public:static Singleton& GetInstance() {static Singleton _instance;return _instance;}void f() { std::cout << "Singleton" << std::endl; }
private:Singleton() {};Singleton(const Singleton&) = delete;Singleton& operator=(const Singleton&) = delete;
};
//使用方式
Singleton::GetInstance().f();
线程不安全,且内存不安全:
#include <iostream>
#include <memory>
class Singleton {
public:static Singleton &GetInstance() {if (_instance == nullptr) {_instance = new Singleton();}return *_instance;}void f() { std::cout << "Singleton" << std::endl; }
private:Singleton() {}Singleton(const Singleton&) = delete;Singleton& operator=(const Singleton&) = delete;static Singleton* _instance;//使用裸指针来存储
};// 静态成员定义
Singleton* Singleton::_instance = nullptr;
使用该代码会导致内存泄漏,如果想手动删除,需要再写一个静态函数进行删除,既然如此,不如改为用智能指针管理;
甚至写成这样:
#pragma once
#include <iostream>
#include <memory>
class Singleton {
public:static Singleton* GetInstance() {//返回指针if (_instance == nullptr) {_instance = new Singleton();}return _instance;}void f() { std::cout << "Singleton" << std::endl; }
private:Singleton() {}Singleton(const Singleton&) = delete;Singleton& operator=(const Singleton&) = delete;static Singleton* _instance;//使用裸指针来存储
};// 静态成员定义
Singleton* Singleton::_instance = nullptr;
可以手动删除单例指针
#include <iostream>
#include "Singleton.h"
using namespace std;
int main()
{Singleton::GetInstance()->f();delete Singleton::GetInstance();//这句话是合法的
}
线程不安全但内存安全(使用智能指针管理):
推荐自己先动手尝试一下如何编写。报错后再看我写的注释就会明白了
我发现很多网上的教程也是直接使用make_shared,make_unique,但是运行后就会发现是错误的,不知道他们写之前有没有跑过...
#pragma once
#include <iostream>
#include <memory>class Singleton {
public://返回的还是引用static Singleton& GetInstance() {if (!_instance)//_instance = std::make_unique<Singleton>();是错误的!!!//因为make_unique无法访问Singleton的私有的构造函数//但是GetInstance是属于Singleton这个类的,所以调用new来构造是允许的_instance = std::unique_ptr<Singleton>(new Singleton());return *_instance; // 返回对象的引用}void f() {std::cout << "Singleton" << std::endl;}//同理,析构函数如果要写,也必须是公有的,因为unique_ptr也无法访问私有的Singleton的析构函数~Singleton() {std::cout << "delete Singleton" << std::endl;}private:static std::unique_ptr<Singleton> _instance;Singleton() {} // 私有构造函数,确保不能通过外部访问Singleton(const Singleton&) = delete; // 禁止拷贝构造Singleton& operator=(const Singleton&) = delete; // 禁止赋值运算符
};
std::unique_ptr<Singleton> Singleton::_instance = nullptr;
那么,GetInstance返回unique_ptr是否可行呢?
#pragma once
#include <iostream>
#include <memory>class Singleton {
public://返回智能指针static std::unique_ptr<Singleton> GetInstance() {if (!_instance)_instance = std::unique_ptr<Singleton>(new Singleton());return std::move(_instance); //不用move会报错}void f() {std::cout << "Singleton" << std::endl;}~Singleton() {std::cout << "delete Singleton" << std::endl;}
private:static std::unique_ptr<Singleton> _instance;Singleton() {} // 私有构造函数,确保不能通过外部访问Singleton(const Singleton&) = delete; // 禁止拷贝构造Singleton& operator=(const Singleton&) = delete; // 禁止赋值运算符
};
std::unique_ptr<Singleton> Singleton::_instance = nullptr;
如果运行会发现能过,但实际上,如果执行:
Singleton::GetInstance()->f();
Singleton::GetInstance()->f();
会得到
因为移动已经把_instance指向的对象移走了,Singleton::GetInstance()执行完后,_instance又变成了nullptr,因此第二次调用单例获取时又重新new了一次。很显然,这样是不符合单例的要求的;
如果一定要返回指针的话,那么就不能用unique_ptr了,要使用shared_ptr
#include <iostream>
#include <memory>class Singleton {
public:static std::shared_ptr<Singleton> GetInstance() {if (!_instance)_instance = std::shared_ptr<Singleton>(new Singleton());return _instance; }void f() {std::cout << "Singleton" << std::endl;}~Singleton() {std::cout << "delete Singleton" << std::endl;}
private:static std::shared_ptr<Singleton> _instance;Singleton() {} // 私有构造函数,确保不能通过外部访问Singleton(const Singleton&) = delete; // 禁止拷贝构造Singleton& operator=(const Singleton&) = delete; // 禁止赋值运算符
};
std::shared_ptr<Singleton> Singleton::_instance = nullptr;
线程安全,内存安全(通过智能指针和锁)
把上面指针指针的方式结合静态变量初始化即可实现线程安全,不过还是演示一下使用锁如何实现线程安全
#include <iostream>
#include <memory>
#include <mutex>
class Singleton {
public:static std::shared_ptr<Singleton> GetInstance() {if (!_instance) {std::lock_guard<mutex> _lock(_mtx);//new的时候上锁,会自动解锁_instance = std::shared_ptr<Singleton>(new Singleton());}return _instance; }void f() {std::cout << "Singleton" << std::endl;}~Singleton() {std::cout << "delete Singleton" << std::endl;}
private:static std::mutex _mtx;static std::shared_ptr<Singleton> _instance;Singleton() {} // 私有构造函数,确保不能通过外部访问Singleton(const Singleton&) = delete; // 禁止拷贝构造Singleton& operator=(const Singleton&) = delete; // 禁止赋值运算符
};
std::shared_ptr<Singleton> Singleton::_instance = nullptr;
或者可以用更高级的once_flag
#include <iostream>
#include <memory>
#include <mutex>
class Singleton {
public:static std::shared_ptr<Singleton> GetInstance() {if (!_instance) {static std::once_flag _flag;std::call_once(_flag, []() {_instance = std::shared_ptr<Singleton>(new Singleton());});}return _instance; }void f() {std::cout << "Singleton" << std::endl;}~Singleton() {std::cout << "delete Singleton" << std::endl;}
private:static std::shared_ptr<Singleton> _instance;Singleton() {} // 私有构造函数,确保不能通过外部访问Singleton(const Singleton&) = delete; // 禁止拷贝构造Singleton& operator=(const Singleton&) = delete; // 禁止赋值运算符
};
std::shared_ptr<Singleton> Singleton::_instance = nullptr;
什么是CRTP?
CRTP(Curiously Recurring Template Pattern,奇异递归模板模式)是一种在 C++ 中广泛使用的设计模式,利用了模板的特性来实现静态多态。它是一种静态多态的实现方式,不同于传统的动态多态(基于虚函数的多态)。
主要思想:
CRTP 模式的核心思想是:类
Derived
作为模板参数传递给基类Base
,使得基类能够访问派生类的成员或调用派生类的函数,而不需要使用虚函数。通过这种方式,基类在编译时就能知道派生类的类型,从而实现静态多态。
下面以动物类为例子,说明CRTP如何实现静态多态
#include <iostream>
template<typename T>
class Animal {//T是Animal的子类
public:void eat(){static_cast<T*>(this)->eat();//调用子类的eat,实现多态}
};
class Dog :public Animal<Dog> {
public:void eat() {std::cout << "dog eat" << std::endl;}
};
Dog d;
Animal<Dog>* ani = &d;
ani->eat();
最终运行结果是dog eat
CRTP 的特点:
-
编译期绑定(静态多态):不像虚函数依赖运行时。
-
无虚函数开销:不需要
vtable
。 -
不支持动态类型转换:没有
dynamic_cast
。 -
一般用来实现策略模式、静态接口检查、静态计数器等。
使用CRTP实现单例基类及子类
#pragma once
#include <iostream>
#include <memory>
#include <mutex>
template<typename T>
class Singleton {//单例基类
public:static std::shared_ptr<T> GetInstance() {static std::once_flag _flag;std::call_once(_flag, [&]() {_instance = std::shared_ptr<T>(new T);//必须使用new T,不能使用make_shared,因为子类T的构造函数也是私有的});return _instance;}~Singleton() {//析构必须是公有的,否则shared_ptr无法帮忙释放内存std::cout << "delete Singleton" << std::endl;}
protected://必须是保护的,否则子类无法使用基类的构造函数Singleton() {}Singleton(const Singleton&) = delete;Singleton& operator=(const Singleton&) = delete;static std::shared_ptr<T> _instance;
};
template<typename T>
std::shared_ptr<T> Singleton<T>::_instance = nullptr;class Derived :public Singleton<Derived> {//真正需要使用的单例类friend class Singleton<Derived>;//必须声明友元,否则在GetInstance无法使用new T
public:~Derived() {//析构必须是公有的std::cout << "delete Derived" << std::endl;}void f() {std::cout << "this is Derived function" << std::endl;}
private:Derived() {}Derived(const Derived&) = delete;Derived& operator=(const Derived&) = delete;
};
使用:
Derived::GetInstance()->f();
一般情况下,只用写好基类Singleton;如果需要将一个类定义成单例,只需继承Singleton<>,并且声明友元,将构造,拷贝设置成私有,将析构设置成共有,即可完成单例类的功能,无需再写一次GetInstance。