能否用C++标准库实现可转移唯一所有权的多副本智能指针?
实现需求的智能指针方案
核心需求拆解
你需要的智能指针要同时满足这些特性:
- 像
std::unique_ptr一样,拥有者出作用域时销毁资源 - 支持多次复制,副本仅作为资源引用,同一时刻仅一个实例持有所有权
- 可从
std::weak_ptr构造,获取临时所有权,出作用域时强制销毁资源并让其他所有指针失效 - 支持
release()转移所有权,实现线程间托管切换
标准库直接实现?不行
C++标准库的智能指针没有直接匹配的:
std::unique_ptr独占所有权,无法复制std::shared_ptr共享所有权,只有最后一个指针析构时才销毁资源std::weak_ptr只是弱引用,无法获取独占所有权
必须基于标准库组件封装,或者自定义控制块实现。
优雅实现方案:自定义twin_ptr(基于控制块)
下面是一个可落地的实现思路,用自定义控制块管理资源、所有权状态和引用计数,结合标准库的原子操作保证线程安全:
核心设计思路
- 控制块(ControlBlock):作为资源的唯一管理者,包含:
std::unique_ptr<T>:存储真正的资源std::atomic<bool>:标记当前是否有活跃所有者std::atomic<int>:跟踪twin_ptr实例的引用计数
- twin_ptr类:封装控制块指针,复制时仅增加控制块的引用计数,不转移所有权
- 所有权规则:
- 只有标记为“所有者”的
twin_ptr析构时,才会销毁资源,并将控制块内的资源置空、所有权标记设为false - 所有副本访问资源前,都会检查控制块的有效性和所有权状态
- 只有标记为“所有者”的
- release()方法:将当前实例的所有权标记转移给目标实例,实现托管切换
简化代码示例
#include <memory> #include <atomic> #include <stdexcept> template<typename T> class twin_ptr { private: struct ControlBlock { std::unique_ptr<T> resource; std::atomic<bool> is_active_owner{false}; std::atomic<int> ref_count{0}; explicit ControlBlock(std::unique_ptr<T> res) : resource(std::move(res)) { is_active_owner = true; ref_count = 1; } void add_ref() { ref_count.fetch_add(1, std::memory_order_relaxed); } bool release_ref() { return ref_count.fetch_sub(1, std::memory_order_acq_rel) == 1; } }; ControlBlock* cb = nullptr; // 私有构造:用于复制场景 explicit twin_ptr(ControlBlock* block) : cb(block) { if (cb) cb->add_ref(); } public: // 从unique_ptr构造,获取初始所有权 explicit twin_ptr(std::unique_ptr<T> res) { if (res) cb = new ControlBlock(std::move(res)); } // 从weak_ptr构造:先lock获取shared_ptr,再转移所有权 // 注意:此操作会让原shared_ptr失效,因为资源被转移到twin_ptr的控制块 explicit twin_ptr(std::weak_ptr<T> wp) { auto sp = wp.lock(); if (sp) { cb = new ControlBlock(std::unique_ptr<T>(sp.release())); } } // 复制构造:共享控制块,不转移所有权 twin_ptr(const twin_ptr& other) : cb(other.cb) { if (cb) cb->add_ref(); } // 复制赋值 twin_ptr& operator=(const twin_ptr& other) { if (this != &other) { cleanup(); cb = other.cb; if (cb) cb->add_ref(); } return *this; } // 移动构造 twin_ptr(twin_ptr&& other) noexcept : cb(other.cb) { other.cb = nullptr; } // 移动赋值 twin_ptr& operator=(twin_ptr&& other) noexcept { if (this != &other) { cleanup(); cb = other.cb; other.cb = nullptr; } return *this; } // 析构函数 ~twin_ptr() { cleanup(); } // 转移所有权到目标twin_ptr void release(twin_ptr& target) { if (!cb || !cb->is_active_owner || this == &target) return; // 确保目标先清理自身旧控制块 target.cleanup(); // 共享当前控制块 target.cb = cb; cb->add_ref(); // 转移所有权标记 cb->is_active_owner.store(false, std::memory_order_release); target.cb->is_active_owner.store(true, std::memory_order_release); } // 资源访问 T* get() const { if (!cb || !cb->is_active_owner || !cb->resource) return nullptr; return cb->resource.get(); } T& operator*() const { auto ptr = get(); if (!ptr) throw std::runtime_error("twin_ptr: invalid resource access"); return *ptr; } T* operator->() const { auto ptr = get(); if (!ptr) throw std::runtime_error("twin_ptr: invalid resource access"); return ptr; } // 检查是否持有所有权 bool has_ownership() const { return cb && cb->is_active_owner.load(std::memory_order_acquire); } // 检查资源是否有效(未被销毁) bool valid() const { return cb && cb->resource != nullptr; } private: void cleanup() { if (!cb) return; bool is_last_ref = cb->release_ref(); // 如果当前是所有者,销毁资源并标记失效 if (cb->is_active_owner.load(std::memory_order_acquire)) { cb->resource.reset(); cb->is_active_owner.store(false, std::memory_order_release); } // 最后一个引用销毁时,释放控制块 if (is_last_ref) delete cb; cb = nullptr; } };
关键特性说明
- 作用域销毁:持有所有权的
twin_ptr析构时,无论是否有其他副本存在,都会立即销毁资源 - 可复制性:副本仅共享控制块,不拥有资源所有权,不会触发资源销毁
- weak_ptr构造:通过lock获取shared_ptr后,将资源转移到控制块,实现临时所有权接管
- 所有权转移:
release()方法通过原子操作切换所有权标记,安全实现线程间托管切换 - 线程安全:控制块内的状态用原子操作更新,避免多线程竞争问题
其他替代思路
如果不想完全自定义,也可以用std::shared_ptr封装一个“所有权代理”类,代理类内部用std::unique_ptr管理资源,配合原子标记跟踪所有者。但本质上和上面的控制块方案逻辑一致,只是实现形式不同。
内容的提问来源于stack exchange,提问作者glades
相关产品推荐
相关产品推荐

