要实现一个安全的C++单例基类,确保子类不会随便覆盖单例行为,我们可以使用一种技巧,即CRTP(Curiously Recurring Template Pattern)。这种模式使得基类能够访问派生类的私有和保护成员,从而允许我们在基类中实现单例逻辑,并且保证派生类不会破坏这个逻辑。
以下是一个使用CRTP实现的单例基类的例子:
#include <iostream> #include <memory> #include <mutex> template<typename Derived> class SingletonBase { private: static std::shared_ptr<Derived> instance_; static std::mutex mtx_; SingletonBase() = default; SingletonBase(const SingletonBase&) = delete; SingletonBase& operator=(const SingletonBase&) = delete; protected: // 提供给派生类访问的静态方法,用于获取单例实例 static std::shared_ptr<Derived> getInstanceDerived() { std::lock_guard<std::mutex> lock(mtx_); if (!instance_) { instance_.reset(new Derived()); } return instance_; } public: // 虚析构函数,允许安全删除通过基类指针指向的派生类对象 virtual ~SingletonBase() = default; // 提供给派生类覆盖的单例获取方法,返回基类指针 static std::shared_ptr<SingletonBase> getInstance() { return std::static_pointer_cast<SingletonBase>(getInstanceDerived()); } }; // 初始化静态成员变量 template<typename Derived> std::shared_ptr<Derived> SingletonBase<Derived>::instance_ = nullptr; template<typename Derived> std::mutex SingletonBase<Derived>::mtx_; // 示例派生类 class MySingleton : public SingletonBase<MySingleton> { public: void doSomething() { std::cout << "Doing something in MySingleton" << std::endl; } }; int main() { // 获取单例实例并调用方法 auto mySingleton = MySingleton::getInstance(); mySingleton->doSomething(); // 尝试直接实例化MySingleton会失败,因为构造函数是私有的 // MySingleton ms; // 编译错误 return 0; }
在这个实现中,SingletonBase
是一个模板类,它接受一个派生类 Derived
作为模板参数。这个基类有一个私有的静态成员变量 instance_
来保存单例实例,以及一个私有的静态互斥锁 mtx_
来保证线程安全。getInstanceDerived()
是一个提供给派生类访问的静态方法,用于创建和返回派生类的单例实例。
getInstance()
方法返回一个基类指针,这允许我们保持单例接口的一致性,即使派生类可能添加额外的功能。注意,我们使用了 std::shared_ptr
来管理单例实例的生命周期,这可以自动处理内存释放,并且允许多个指针指向同一个实例。
由于 SingletonBase
的构造函数、拷贝构造函数和赋值运算符都是私有的或删除的,因此派生类无法直接创建或复制 SingletonBase
的实例。getInstanceDerived()
方法只能被 SingletonBase
和其派生类访问,因此派生类不能覆盖这个方法来破坏单例的逻辑。
这样,通过继承 SingletonBase<MySingleton>
,MySingleton
类自动成为了一个线程安全的单例类,而无需担心子类会覆盖关键的单例行为。