如何包装STL容器并实现正确的const与非const行为?
包装存储智能指针的STL容器时如何保证const正确性?
当包装存储智能指针类型(如std::shared_ptr<SomeType>)的STL容器(如std::vector<T>)时,能否通过简便方式同时保证const正确性并启用迭代器的const重载?
以下是示例代码,其中被const修饰的MyContainerWrapper对象的迭代器仍可修改元素的value成员:
#include <memory> #include <vector> #include <iostream> #include <cstdio> class SomeType { public: using Ptr = std::shared_ptr<SomeType>; using ConstPtr = std::shared_ptr<const SomeType>; SomeType(double v) : value(v) {} double value; }; class MyContainerWrapper { public: using Container = std::vector<SomeType::Ptr>; MyContainerWrapper(size_t size) : _data(size, SomeType::Ptr(new SomeType(1.23))) {} void add(double v) { _data.emplace_back(new SomeType(v)); } // 非const迭代器 typename Container::iterator begin() { printf("b\n"); return _data.begin(); } typename Container::iterator end() { printf("e\n"); return _data.end(); } // 原const迭代器重载:仅保护容器内的智能指针不被修改,仍能修改指向的对象 typename Container::const_iterator begin() const { printf("cb\n"); return _data.begin(); } typename Container::const_iterator end() const { printf("ce\n"); return _data.end(); } protected: Container _data; }; void modifier(const MyContainerWrapper& arg) { for (const auto& element : arg) { element->value = 12.345; // 此处希望禁止修改,但当前代码允许 } } int main(int argc, char const *argv[]) { MyContainerWrapper cont(10); modifier(cont); return 0; }
问题根源
原代码中Container::const_iterator仅保证不能修改容器内的智能指针本身(比如不能替换指针实例),但智能指针指向的SomeType对象仍然是非const的,因此可以通过element->value修改成员。我们需要让const容器的迭代器返回SomeType::ConstPtr,从而限制对指向对象的修改。
解决方案(无需自定义迭代器/重复容器)
方案1:使用C标准库的std::transform_iterator(C11及以上)
通过std::transform_iterator将容器内的SomeType::Ptr转换为SomeType::ConstPtr,无需编写完整的自定义迭代器:
#include <memory> #include <vector> #include <iostream> #include <cstdio> #include <iterator> // 包含transform_iterator class SomeType { public: using Ptr = std::shared_ptr<SomeType>; using ConstPtr = std::shared_ptr<const SomeType>; SomeType(double v) : value(v) {} double value; }; class MyContainerWrapper { public: using Container = std::vector<SomeType::Ptr>; // 定义转换后的const迭代器类型 using ConstIterator = std::transform_iterator< SomeType::ConstPtr(*)(SomeType::Ptr), typename Container::const_iterator >; MyContainerWrapper(size_t size) : _data(size, SomeType::Ptr(new SomeType(1.23))) {} void add(double v) { _data.emplace_back(new SomeType(v)); } // 非const迭代器保持不变 typename Container::iterator begin() { printf("b\n"); return _data.begin(); } typename Container::iterator end() { printf("e\n"); return _data.end(); } // 修改后的const迭代器:返回转换为ConstPtr的迭代器 ConstIterator begin() const { printf("cb\n"); // 利用shared_ptr的隐式转换,将Ptr转为ConstPtr return ConstIterator(_data.begin(), [](SomeType::Ptr p) -> SomeType::ConstPtr { return p; }); } ConstIterator end() const { printf("ce\n"); return ConstIterator(_data.end(), [](SomeType::Ptr p) -> SomeType::ConstPtr { return p; }); } protected: Container _data; }; void modifier(const MyContainerWrapper& arg) { for (const auto& element : arg) { // element 是 SomeType::ConstPtr,此处修改会触发编译错误 // element->value = 12.345; } } int main(int argc, char const *argv[]) { MyContainerWrapper cont(10); modifier(cont); return 0; }
方案2:使用C++20的范围视图(更简洁)
如果你的项目支持C++20,可以用std::ranges::transform_view直接生成转换后的范围,代码更简洁:
#include <memory> #include <vector> #include <iostream> #include <cstdio> #include <ranges> // 包含范围视图 class SomeType { public: using Ptr = std::shared_ptr<SomeType>; using ConstPtr = std::shared_ptr<const SomeType>; SomeType(double v) : value(v) {} double value; }; class MyContainerWrapper { public: using Container = std::vector<SomeType::Ptr>; MyContainerWrapper(size_t size) : _data(size, SomeType::Ptr(new SomeType(1.23))) {} void add(double v) { _data.emplace_back(new SomeType(v)); } // 非const迭代器保持不变 typename Container::iterator begin() { printf("b\n"); return _data.begin(); } typename Container::iterator end() { printf("e\n"); return _data.end(); } // 利用transform_view将Ptr转为ConstPtr auto begin() const { printf("cb\n"); auto view = std::ranges::transform_view(_data, [](SomeType::Ptr p) -> SomeType::ConstPtr { return p; }); return view.begin(); } auto end() const { printf("ce\n"); auto view = std::ranges::transform_view(_data, [](SomeType::Ptr p) -> SomeType::ConstPtr { return p; }); return view.end(); } protected: Container _data; }; void modifier(const MyContainerWrapper& arg) { for (const auto& element : arg) { // element 是 SomeType::ConstPtr,修改会触发编译错误 // element->value = 12.345; } } int main(int argc, char const *argv[]) { MyContainerWrapper cont(10); modifier(cont); return 0; }
原理说明
两种方案都是通过转换迭代器/范围,将容器内的std::shared_ptr<SomeType>隐式转换为std::shared_ptr<const SomeType>,这样当通过const容器的迭代器访问元素时,得到的是指向const对象的智能指针,自然无法修改SomeType的非mutable成员,从而保证了const正确性。
内容的提问来源于stack exchange,提问作者joaocandre
相关产品推荐
相关产品推荐

