如何使用shared_ptr实现带私有析构函数的C++单例?
错误原因
你遇到的编译错误本质是:std::shared_ptr的默认删除器无法访问Singleton2的私有析构函数。当shared_ptr销毁托管对象时,会调用标准库提供的std::default_delete<T>,而这个删除器的代码不属于你的类作用域,因此没有权限调用私有析构函数。
你的unique_ptr版本能正常工作,大概率是因为其删除器的调用上下文处于类的作用域内,或是你已经为它配置了可访问私有析构的自定义删除器。
可行解决方案
方案1:自定义内部删除器
在单例类内部定义删除器结构体,利用内部类天然的访问权限调用私有析构:
#include <memory> class Singleton2 { private: Singleton2() = default; ~Singleton2() = default; // 内部删除器,可直接访问私有成员 struct Deleter { void operator()(Singleton2* ptr) const { delete ptr; } }; public: static std::shared_ptr<Singleton2> getInstance() { static std::shared_ptr<Singleton2> instance(new Singleton2(), Deleter()); return instance; } // 禁止拷贝、移动操作 Singleton2(const Singleton2&) = delete; Singleton2& operator=(const Singleton2&) = delete; Singleton2(Singleton2&&) = delete; Singleton2& operator=(Singleton2&&) = delete; };
方案2:将默认删除器设为友元
直接授权std::default_delete<Singleton2>访问私有析构,让shared_ptr可以使用默认删除器:
#include <memory> class Singleton2 { private: Singleton2() = default; ~Singleton2() = default; // 让标准库默认删除器有权访问私有析构 friend std::default_delete<Singleton2>; public: static std::shared_ptr<Singleton2> getInstance() { static std::shared_ptr<Singleton2> instance(new Singleton2()); return instance; } // 禁止拷贝、移动操作 Singleton2(const Singleton2&) = delete; Singleton2& operator=(const Singleton2&) = delete; Singleton2(Singleton2&&) = delete; Singleton2& operator=(Singleton2&&) = delete; };
方案3:静态局部对象+空删除器
如果不需要动态分配内存,可使用静态局部对象,配合空删除器避免shared_ptr误删(静态对象会在程序结束时自动销毁):
#include <memory> class Singleton2 { private: Singleton2() = default; ~Singleton2() = default; public: static std::shared_ptr<Singleton2> getInstance() { // C++11及以后,静态局部对象初始化线程安全 static Singleton2 instance; // 空删除器:告诉shared_ptr无需销毁对象 static std::shared_ptr<Singleton2> ptr(&instance, [](Singleton2*){}); return ptr; } // 禁止拷贝、移动操作 Singleton2(const Singleton2&) = delete; Singleton2& operator=(const Singleton2&) = delete; Singleton2(Singleton2&&) = delete; Singleton2& operator=(Singleton2&&) = delete; };
各方案对比
- 方案1:自定义删除器逻辑独立,不依赖标准库细节,适合需要自定义销毁逻辑的场景。
- 方案2:代码最简洁,直接利用友元授权,适合常规单例场景。
- 方案3:无动态分配开销,性能最优,适合对内存分配敏感的场景,但需注意静态对象的生命周期管理。
内容的提问来源于stack exchange,提问作者StarvingDays
相关产品推荐
相关产品推荐

