C++单例模式进阶:如何禁止复制指向单例对象的指针?
一、能否阻止创建指向单例实例的多个指针副本?
首先明确:无法直接阻止裸指针的拷贝,因为指针是C++的内置类型,语言层面没有提供禁用指针赋值/拷贝的机制。你示例中的Singleton *q = Singleton::Instance();和auto A = p;这类操作,本质是拷贝内存地址值,属于语言原生支持的行为,没法从语法上直接禁止。
但可以通过设计接口来避免用户拿到裸指针,从根源上减少这种情况:
返回引用而非指针
经典单例实现通常返回实例的引用,而非裸指针,这样用户无法轻易获取指针进行拷贝:class Singleton { private: Singleton() = default; Singleton(const Singleton&) = delete; Singleton& operator=(const Singleton&) = delete; public: static Singleton& Instance() { static Singleton instance; // 局部静态变量保证唯一实例 return instance; } };此时用户只能通过
Singleton::Instance()访问实例,无法直接拿到指针(除非主动用&取地址,但这属于用户刻意操作,无法完全避免)。自定义不可拷贝的访问器类
如果要彻底阻止用户拷贝实例的访问权,可以封装一个不可拷贝的访问器,仅允许单次获取实例的操作权限:class Singleton { private: Singleton() = default; Singleton(const Singleton&) = delete; Singleton& operator=(const Singleton&) = delete; ~Singleton() = default; // 自定义访问器,禁止拷贝 class Accessor { private: Singleton* ptr; Accessor(Singleton* p) : ptr(p) {} Accessor(const Accessor&) = delete; Accessor& operator=(const Accessor&) = delete; friend class Singleton; public: // 重载运算符,让访问器能像指针/引用一样使用 Singleton& operator*() const { return *ptr; } Singleton* operator->() const { return ptr; } }; public: static Accessor Instance() { static Singleton instance; return Accessor(&instance); } };此时
auto q = Singleton::Instance();会直接编译失败,因为Accessor的拷贝构造已被删除;用户也无法通过Accessor拷贝出额外的指针访问权。
二、能否在创建第一个对象后删除默认构造函数?
不行,因为构造函数的delete是编译期属性,无法在运行时动态修改。你示例中在构造函数内执行Singleton() = delete;完全无效——这行代码是运行时执行的,而构造函数的可用性在编译阶段就已经确定了。
如果要实现“仅允许创建一个实例”,可以通过运行时检查来阻止后续创建:
#include <stdexcept> class Singleton { private: static bool instance_created; public: Singleton() { if (instance_created) { throw std::runtime_error("只能创建一个Singleton实例"); // 也可以直接调用std::terminate()终止程序 } instance_created = true; } Singleton(const Singleton&) = delete; Singleton& operator=(const Singleton&) = delete; ~Singleton() { // 如果需要允许实例销毁后重新创建,可以重置标记 instance_created = false; } }; // 初始化静态标记 bool Singleton::instance_created = false;
这种方式能在运行时阻止第二个实例的创建,但无法在编译期提前报错。如果需要编译期保证唯一实例,还是得用经典单例模式——把构造函数设为私有,仅通过静态方法Instance()提供唯一实例。
内容的提问来源于stack exchange,提问作者niil87

