如何从std::set获取可变元素且不破坏其排序规则?
问题描述
我定义了包含实际数据与ID的结构体Data:
struct Data { int value; int DataId; };
我需要频繁根据ID从Data集合中获取元素并修改其value(但不修改ID),因此选择std::set以实现高效排序,并使用如下透明比较器:
class Comparator { /// dummy type for declaring is_transparent type struct SIsTransparentTag; public: // 标记透明比较器,允许std::set::find直接用int作为key查找 using is_transparent = SIsTransparentTag; // 两个Data对象间的比较 bool operator()(const Data &lhs, const Data &rhs) const { return lhs.DataId < rhs.DataId; } // Data对象与ID的比较 bool operator()(const Data &lhs, const int id) const { return lhs.DataId < id; } // ID与Data对象的比较 bool operator()(const int id, const Data &rhs) const { return id < rhs.DataId; } };
随后编写的读写获取函数存在未定义行为:
Data* GetRWDataFromID(std::set<Data,Comparator> &collection,int id) { auto it = collection.find(id); if (it == std::end(collection)) { return nullptr; } // 此处const_cast去除const属性后修改对象会触发未定义行为 return const_cast<Data *>(&(*it)); }
核心矛盾:std::set的迭代器指向的是不可变元素,但我仅需修改不影响集合排序的value成员。
需求:
- 保持对数时间的高效查找
- ID与数据绑定(获取数据时可直接读取ID,无需额外记录,避免使用
std::map) DataId在Data对象创建后不可变(可设为私有并提供只读访问器)
解决方案
方法1:将value声明为mutable
这是最直接的方案:把Data中的value标记为mutable,允许在const上下文中修改它;同时把DataId设为const私有成员,通过构造函数初始化,保证创建后不可修改。
修改后的Data结构体
struct Data { private: const int DataId; // const保证创建后不可修改 public: mutable int value; // mutable允许const迭代器修改该成员 // 构造函数初始化ID和value,确保ID只能在创建时设置 Data(int id, int val) : DataId(id), value(val) {} // 只读访问器,对外暴露ID int getId() const { return DataId; } };
调整后的比较器
class Comparator { struct SIsTransparentTag; public: using is_transparent = SIsTransparentTag; bool operator()(const Data &lhs, const Data &rhs) const { return lhs.getId() < rhs.getId(); } bool operator()(const Data &lhs, const int id) const { return lhs.getId() < id; } bool operator()(const int id, const Data &rhs) const { return id < rhs.getId(); } };
安全的获取函数
此时无需const_cast,直接通过const迭代器修改mutable成员即可,完全符合标准:
// 或者直接在函数内完成修改,避免返回指针 void UpdateDataValue(std::set<Data,Comparator> &collection,int id, int new_val) { auto it = collection.find(id); if (it != std::end(collection)) { it->value = new_val; // 合法修改mutable成员 } } // 如果需要返回可操作的指针 Data* GetRWDataFromID(std::set<Data,Comparator> &collection,int id) { auto it = collection.find(id); if (it == std::end(collection)) { return nullptr; } // 此处const_cast仅用于暴露指针,修改value是合法的 return const_cast<Data*>(&(*it)); }
方法2:用std::set存储std::unique_ptr<Data>
如果不想使用mutable,可以让std::set存储智能指针。std::set的元素是不可变的指针,但指针指向的Data对象可以修改(只要保证DataId不变)。
修改后的Data结构体
struct Data { private: const int DataId; public: int value; Data(int id, int val) : DataId(id), value(val) {} int getId() const { return DataId; } };
调整后的比较器
比较器需要基于指针指向的Data对象的ID进行排序:
class Comparator { struct SIsTransparentTag; public: using is_transparent = SIsTransparentTag; bool operator()(const std::unique_ptr<Data>& lhs, const std::unique_ptr<Data>& rhs) const { return lhs->getId() < rhs->getId(); } bool operator()(const std::unique_ptr<Data>& lhs, const int id) const { return lhs->getId() < id; } bool operator()(const int id, const std::unique_ptr<Data>& rhs) const { return id < rhs->getId(); } };
获取函数
直接返回指针指向的Data对象,无需任何类型转换:
Data* GetRWDataFromID(std::set<std::unique_ptr<Data>, Comparator>& collection, int id) { auto it = collection.find(id); if (it == collection.end()) { return nullptr; } return it->get(); // 返回可修改的Data指针 }
这种方案的优势是Data结构不需要特殊标记,内存管理由std::unique_ptr自动处理。
内容的提问来源于stack exchange,提问作者Oersted
相关产品推荐
相关产品推荐

