如何在CRTP单例模式下禁止TestSingleton外部实例化(非友元方式)
替代CRTP单例中私有构造+友元的实现方案
原实现通过将子类构造设为私有并声明基类为友元来禁止外部实例化,以下是几种无需友元的替代方案:
方案一:基于受保护标签参数的构造函数
核心思路是通过一个仅基类和子类可访问的标签类型,限制构造函数的调用权限,外部代码无法创建该标签的实例,因此无法直接构造子类对象。
实现代码
#include <stdio.h> template <typename T> class Singleton { public: static T &GetInstance() { // 使用基类的受保护标签构造子类实例 static T instance{PrivateTag{}}; return instance; } protected: // 仅基类和子类可访问的标签类型 struct PrivateTag {}; Singleton() = default; private: // 删除拷贝/移动操作,避免额外实例被创建 Singleton(const Singleton &) = delete; Singleton &operator=(const Singleton &) = delete; Singleton(Singleton &&) = delete; Singleton &operator=(Singleton &&) = delete; }; class TestSingleton : public Singleton<TestSingleton> { public: void print() { printf("%p TestSingleton\n", this); } // 仅接受受保护标签的构造函数,外部无法调用 TestSingleton(PrivateTag) = default; // 删除默认构造函数,禁止外部无参构造 TestSingleton() = delete; }; inline TestSingleton &GetTestSingleton() { printf("func TestSingleton\n"); return TestSingleton::GetInstance(); } int main() { // 编译错误:默认构造函数已删除 // TestSingleton().print(); GetTestSingleton().print(); GetTestSingleton().print(); GetTestSingleton().print(); // 编译错误:拷贝构造被基类禁止 // TestSingleton test_copy = GetTestSingleton(); return 0; }
方案说明
- 基类
Singleton<T>定义了protected的PrivateTag结构体,只有基类和继承它的子类能访问该类型; - 子类
TestSingleton只保留接受PrivateTag参数的构造函数,并删除默认构造函数; - 外部代码无法创建
PrivateTag实例(因为无法访问protected类型),因此无法直接构造TestSingleton对象; - 基类的
GetInstance函数可以创建PrivateTag实例,从而合法构造子类对象,无需声明友元。
方案二:将子类构造函数设为受保护(protected)
如果不需要禁止子类被进一步继承,可以将子类的构造函数设为protected,外部代码无法直接调用protected构造函数创建对象,而基类的静态成员函数可以通过继承关系合法构造子类实例。
实现代码
#include <stdio.h> template <typename T> class Singleton { public: static T &GetInstance() { static T instance; return instance; } protected: Singleton() = default; private: Singleton(const Singleton &) = delete; Singleton &operator=(const Singleton &) = delete; Singleton(Singleton &&) = delete; Singleton &operator=(Singleton &&) = delete; }; class TestSingleton : public Singleton<TestSingleton> { public: void print() { printf("%p TestSingleton\n", this); } protected: // 设为受保护构造,外部无法直接实例化 TestSingleton() = default; }; inline TestSingleton &GetTestSingleton() { printf("func TestSingleton\n"); return TestSingleton::GetInstance(); } int main() { // 编译错误:无法访问受保护构造函数 // TestSingleton().print(); GetTestSingleton().print(); GetTestSingleton().print(); GetTestSingleton().print(); // 编译错误:拷贝构造被基类禁止 // TestSingleton test_copy = GetTestSingleton(); return 0; }
方案说明
- 子类构造函数设为
protected后,外部代码无法直接创建对象; - 基类
Singleton<T>的GetInstance函数作为基类的静态成员,可以合法构造子类实例; - 该方案无需友元声明,若要禁止子类被继承,可在子类类名后添加
final关键字。
内容的提问来源于stack exchange,提问作者justin
相关产品推荐
相关产品推荐

