从std::unique_ptr集合返回原始指针的最优实现方案
最优实现方案分析
你提出的双集合存储方案确实存在冗余且容易引发同步问题,下面是几种更合理的实现方式,按推荐优先级排序:
方案1:C++20 范围视图(推荐)
无需额外存储,通过视图动态将unique_ptr<B>转换为原始指针,完全复用原集合的元素,不存在同步风险,符合零开销原则。
#include <set> #include <memory> #include <ranges> class B {}; class A { private: std::set<std::unique_ptr<B>> _Bs; public: // 返回一个视图,元素为B*,直接映射原集合 auto getBs() const { return _Bs | std::views::transform([](const auto& ptr) { return ptr.get(); }); } // 示例:添加元素的接口,维护所有权 void addB(std::unique_ptr<B> b) { _Bs.insert(std::move(b)); } }; // 使用示例 int main() { A a; a.addB(std::make_unique<B>()); a.addB(std::make_unique<B>()); for (B* b : a.getBs()) { // 直接使用原始指针,不转移所有权 } }
方案2:自定义迭代器适配器(兼容C++11及以上)
如果无法使用C++20,可以封装迭代器适配器,实现从unique_ptr<B>到B*的转换,同样无需额外存储。
#include <set> #include <memory> #include <iterator> class B {}; // 迭代器适配器:将unique_ptr<B>迭代器转换为B*迭代器 class RawPtrIter { public: using iterator_category = std::bidirectional_iterator_tag; using value_type = B*; using difference_type = std::ptrdiff_t; using pointer = B**; using reference = B*&; explicit RawPtrIter(std::set<std::unique_ptr<B>>::const_iterator it) : _it(it) {} B* operator*() const { return _it->get(); } B* operator->() const { return _it->get(); } RawPtrIter& operator++() { ++_it; return *this; } RawPtrIter operator++(int) { auto temp = *this; ++_it; return temp; } RawPtrIter& operator--() { --_it; return *this; } RawPtrIter operator--(int) { auto temp = *this; --_it; return temp; } bool operator==(const RawPtrIter& other) const { return _it == other._it; } bool operator!=(const RawPtrIter& other) const { return !(*this == other); } private: std::set<std::unique_ptr<B>>::const_iterator _it; }; // 范围包装类,提供begin/end接口 class RawBsRange { public: explicit RawBsRange(const std::set<std::unique_ptr<B>>& bs) : _bs(bs) {} RawPtrIter begin() const { return RawPtrIter(_bs.begin()); } RawPtrIter end() const { return RawPtrIter(_bs.end()); } private: const std::set<std::unique_ptr<B>>& _bs; }; class A { private: std::set<std::unique_ptr<B>> _Bs; public: RawBsRange getBs() const { return RawBsRange(_Bs); } void addB(std::unique_ptr<B> b) { _Bs.insert(std::move(b)); } }; // 使用示例 int main() { A a; a.addB(std::make_unique<B>()); for (B* b : a.getBs()) { // 使用原始指针 } }
不推荐的方案:维护双集合
你最初的双集合方案存在明显缺陷:
- 同步风险:每次添加、删除
_Bs中的元素时,必须手动同步_rawBs,极易遗漏导致集合不一致。 - 内存浪费:额外存储一份指针集合,对大型集合完全没必要。
- 野指针风险:若
_Bs中的unique_ptr被释放,_rawBs中的指针会变成野指针,除非严格同步,但同步逻辑复杂度高。
如果因接口限制必须返回std::set<B*>&,可以用缓存+失效标记的方式优化,但仍不推荐:
#include <set> #include <memory> #include <algorithm> class B {}; class A { private: std::set<std::unique_ptr<B>> _Bs; mutable std::set<B*> _rawBs; mutable bool _cacheValid = false; void syncCache() const { if (!_cacheValid) { _rawBs.clear(); std::transform(_Bs.begin(), _Bs.end(), std::inserter(_rawBs, _rawBs.begin()), [](const auto& ptr) { return ptr.get(); }); _cacheValid = true; } } public: std::set<B*>& getBs() { syncCache(); return _rawBs; } void addB(std::unique_ptr<B> b) { _Bs.insert(std::move(b)); _cacheValid = false; // 标记缓存失效 } void removeB(B* target) { auto it = std::find_if(_Bs.begin(), _Bs.end(), [target](const auto& ptr) { return ptr.get() == target; }); if (it != _Bs.end()) { _Bs.erase(it); _cacheValid = false; } } };
内容的提问来源于stack exchange,提问作者SirXanderRoss
相关产品推荐
相关产品推荐

