如何使expiring_map的const迭代器禁止修改value_type的second成员?
解决const迭代器可修改mutable成员的问题
要解决你的问题,核心是让const expiring_map的迭代器返回的对象中,second成员是不可修改的const T&,同时兼容::testing::ElementsAre的要求,不用手动复制std::pair的构造函数。可以通过自定义迭代器适配器+代理对象实现,具体步骤如下:
1. 定义兼容std::pair的value_type
让你的value_type直接继承std::pair<const Key, T>,自动继承所有构造函数,同时显式将second声明为mutable,保留普通迭代器的修改权限:
template <typename Key, typename T> struct expiring_map_value : std::pair<const Key, T> { // 继承std::pair的所有构造函数 using std::pair<const Key, T>::pair; // 显式将second声明为mutable,覆盖std::pair的const限制 mutable T& second = std::pair<const Key, T>::second; };
2. 实现const迭代器的代理对象
这个代理对象包装const value_type,在operator->返回的视图中把second转为const,同时支持隐式转换为value_type,满足ElementsAre的类型断言需求:
template <typename ValueType> struct const_value_proxy { const ValueType* ptr; explicit const_value_proxy(const ValueType* p) : ptr(p) {} // 代理的箭头操作返回只读视图 struct const_view { const typename ValueType::first_type& first; const typename ValueType::second_type& second; explicit const_view(const ValueType& v) : first(v.first), second(v.second) {} }; const_view operator->() const { return const_view(*ptr); } // 支持转换为value_type,适配ElementsAre的断言逻辑 operator ValueType() const { return *ptr; } };
3. 用boost::iterator_adaptor包装const迭代器
基于Boost的迭代器适配器,把multi_index的原生const迭代器包装成自定义const迭代器,使其返回上述代理对象:
#include <boost/iterator/iterator_adaptor.hpp> template <typename MultiIndexConstIter> class expiring_map_const_iterator : public boost::iterator_adaptor< expiring_map_const_iterator<MultiIndexConstIter>, MultiIndexConstIter, const expiring_map_value<typename MultiIndexConstIter::value_type::first_type, typename MultiIndexConstIter::value_type::second_type>, boost::use_default, const_value_proxy<typename MultiIndexConstIter::value_type> > { public: using base_type = boost::iterator_adaptor< expiring_map_const_iterator<MultiIndexConstIter>, MultiIndexConstIter, const expiring_map_value<typename MultiIndexConstIter::value_type::first_type, typename MultiIndexConstIter::value_type::second_type>, boost::use_default, const_value_proxy<typename MultiIndexConstIter::value_type> >; expiring_map_const_iterator() = default; explicit expiring_map_const_iterator(MultiIndexConstIter it) : base_type(it) {} private: friend class boost::iterator_core_access; const_value_proxy<typename MultiIndexConstIter::value_type> dereference() const { return const_value_proxy<typename MultiIndexConstIter::value_type>(&(*this->base())); } };
4. 在expiring_map中集成自定义迭代器
修改expiring_map类,定义普通迭代器和自定义const迭代器,并实现对应的begin()/end()方法,同时保留过期元素清理逻辑:
template <typename Key, typename T, typename Clock = std::chrono::steady_clock> class expiring_map { private: // 带过期时间的元素结构 struct mapped_with_expiry { expiring_map_value<Key, T> value; typename Clock::time_point expiry; }; // multi_index容器定义(按key唯一排序+按过期时间排序) using container_type = boost::multi_index::multi_index_container< mapped_with_expiry, boost::multi_index::indexed_by< boost::multi_index::ordered_unique< boost::multi_index::member<mapped_with_expiry, expiring_map_value<Key, T>, &mapped_with_expiry::value>, [](const auto& a, const auto& b) { return a.first < b.first; } >, boost::multi_index::ordered_non_unique< boost::multi_index::member<mapped_with_expiry, typename Clock::time_point, &mapped_with_expiry::expiry> > > >; mutable container_type container_; // mutable允许const方法清理过期元素 // 清理过期元素的内部方法 void cleanup_expired() { const auto now = Clock::now(); auto& expiry_idx = container_.template get<1>(); expiry_idx.erase(expiry_idx.begin(), expiry_idx.upper_bound(now)); } public: // 迭代器类型定义 using iterator = typename container_type::template nth_index<0>::type::iterator; using const_iterator = expiring_map_const_iterator<typename container_type::template nth_index<0>::type::const_iterator>; // 普通迭代器方法 iterator begin() { cleanup_expired(); return container_.template get<0>().begin(); } iterator end() { cleanup_expired(); return container_.template get<0>().end(); } // const迭代器方法 const_iterator begin() const { cleanup_expired(); return const_iterator(container_.template get<0>().cbegin()); } const_iterator end() const { cleanup_expired(); return const_iterator(container_.template get<0>().cend()); } // 示例insert方法(结合过期时间逻辑) template <typename K, typename V, typename Duration> std::pair<iterator, bool> insert(K&& key, V&& value, Duration expiry_duration) { cleanup_expired(); auto [it, inserted] = container_.template get<0>().insert({ {std::forward<K>(key), std::forward<V>(value)}, Clock::now() + expiry_duration }); return {it, inserted}; } // 其他std::map风格接口(如find、erase等)可按需实现 };
效果验证
现在你的测试代码:
using subject_type = ::expiring_map<std::shared_ptr<std::chrono::steady_clock>, int, std::string>; void foo(const subject_type& s) { s.begin()->second = "new"; // 编译错误:无法给const对象赋值 }
会触发编译错误,符合预期。同时const expiring_map可以正常用于范围for循环,::testing::ElementsAre也能正常工作,因为代理对象可以隐式转换为expiring_map_value。
内容的提问来源于stack exchange,提问作者SherAndrei
相关产品推荐
相关产品推荐

