如何实现编译期固定最大引用计数的共享指针?
编译期限制最大引用计数的共享指针实现方案
要实现编译期固定最大引用计数的共享指针,不能依赖std::shared_ptr的运行期计数逻辑,得靠C++模板类型系统在编译阶段卡死拷贝次数——用模板参数指定最大引用数,通过拷贝构造/赋值的重载约束,让超过限额的拷贝直接触发编译错误。
实现思路
核心是用编译期模板参数跟踪剩余可拷贝次数:
- 用外层模板参数
MaxRef指定最大引用总数(比如你的场景是2) - 内部嵌套一个带非类型模板参数
Remaining的impl类,标记当前实例还能被拷贝多少次 - 只有当
Remaining > 0时,才允许拷贝构造/赋值;拷贝后新实例的Remaining自动减1,原实例的可用次数同步消耗 - 用C20的
requires表达式(或C17及更早的static_assert+特化)做编译期检查,不满足条件直接报错
完整实现代码
#include <memory> #include <utility> template <size_t MaxRef, typename T> class limited_shared_ptr { private: template <size_t Remaining> class impl { public: std::shared_ptr<T> ptr; // 从原始指针/unique_ptr构造 explicit impl(T* raw_ptr) : ptr(raw_ptr) {} explicit impl(std::unique_ptr<T>&& u_ptr) : ptr(std::move(u_ptr)) {} // 移动构造/赋值:不受拷贝次数限制 impl(impl&&) noexcept = default; impl& operator=(impl&&) noexcept = default; // 拷贝构造:仅当还有剩余拷贝名额时允许 impl(const impl& other) requires (Remaining > 0) : ptr(other.ptr) {} // 拷贝赋值:仅当还有剩余拷贝名额时允许 impl& operator=(const impl& other) requires (Remaining > 0) { ptr = other.ptr; return *this; } // 生成新实例并消耗一个拷贝名额 impl<Remaining - 1> copy() const requires (Remaining > 0) { return impl<Remaining - 1>(ptr); } // 重载常用操作符 T& operator*() const { return *ptr; } T* operator->() const { return ptr.get(); } }; impl<MaxRef - 1> m_impl; // 自身占一个引用,剩余可拷贝次数为MaxRef-1 public: // 构造函数 explicit limited_shared_ptr(T* raw_ptr) : m_impl(raw_ptr) {} explicit limited_shared_ptr(std::unique_ptr<T>&& u_ptr) : m_impl(std::move(u_ptr)) {} // 拷贝构造:自动消耗一个名额 limited_shared_ptr(const limited_shared_ptr& other) : m_impl(other.m_impl.copy()) {} // 拷贝赋值:自动消耗一个名额 limited_shared_ptr& operator=(const limited_shared_ptr& other) { m_impl = other.m_impl.copy(); return *this; } // 移动构造/赋值:默认实现 limited_shared_ptr(limited_shared_ptr&&) noexcept = default; limited_shared_ptr& operator=(limited_shared_ptr&&) noexcept = default; // 暴露常用接口 T& operator*() const { return *m_impl; } T* operator->() const { return m_impl.operator->(); } T* get() const { return m_impl.ptr.get(); } }; // 辅助创建函数,类似std::make_shared template <size_t MaxRef, typename T, typename... Args> limited_shared_ptr<MaxRef, T> make_limited_shared(Args&&... args) { return limited_shared_ptr<MaxRef, T>(new T(std::forward<Args>(args)...)); } // 测试用例 int main() { const auto p = make_limited_shared<2, double>(2159); // 正常编译 const auto q = p; // 正常编译 // const auto err1 = p; // 编译错误:剩余拷贝次数为0,不满足requires约束 // const auto err2 = q; // 编译错误:q的剩余拷贝次数为0,无法拷贝 return 0; }
针对你的队列场景的适配
把你的队列类型替换掉代码里的double,用limited_shared_ptr<2, YourQueueType>来封装队列实例:
- 写入线程持有第一个实例,读线程持有通过拷贝得到的第二个实例
- 任何尝试创建第三个实例的操作都会直接触发编译错误,从根源上保证不会出现超过两个线程同时持有队列的情况,完美匹配你单读单写的需求
兼容性说明
如果你的项目还在使用C++17或更早版本,可以把requires表达式换成static_assert结合模板特化的方式:
- 为
Remaining=0的impl类显式禁用拷贝构造和赋值 - 在拷贝构造函数里加入
static_assert(Remaining > 0, "Exceeded maximum reference count")
内容的提问来源于stack exchange,提问作者SpeakX
相关产品推荐
相关产品推荐

