如何为类内关联的set容器指定自定义比较器?
问题描述
需要为MyClass类的私有成员mbp(类型为map<unsigned, set<string, Compare>>)中的set容器指定自定义比较器。该比较器不直接比较string类型的键,而是通过键访问类内的ob成员(map<string, MyStruct>),获取对应MyStruct的p和t属性,按照先按p降序,p相同时按t升序的规则完成比较。
尝试过类内结构体、仿函数、bind三种实现方式均出错,需找到可行方案以节省内存。
代码示例:
struct MyStruct { unsigned p; unsigned t; }; class MyClass { public: void call() { ob["o1_10"] ={10, 1}; mbp[ob["o1_10"].p].insert("o1_10"); ob["o2_20_2"] ={20, 2}; mbp[ob["o2_20"].p].insert("o2_20"); ob["o4_4_4"] ={4, 4}; mbp[ob["o4_4"].p].insert("o4_4"); ob["o5_10"] ={10, 4}; mbp[ob["o5_10"].p].insert("o5_10"); } private: map<unsigned,set<string, Compare>> mbp; // 需求:通过自定义比较器,利用ob中存储的MyStruct的p和t属性进行比较,而非直接比较string键 // 示例逻辑: // bool compare(const string& o1, const string& o2) const { // if (ob.at(o1).p > ob.at(o2).p) return true; // if (ob.at(o1).p == ob.at(o2).p && ob.at(o1).t < ob.at(o2).t) return true; // return false; // } map<string, MyStruct> ob; }; int main() { MyClass my_class; my_class.call(); return 0; }
注:比较逻辑依赖通过string键访问类内ob中的MyStruct实例属性,而非直接比较string本身。
可行实现方案
核心思路
自定义带状态的仿函数作为比较器,让其持有MyClass实例的const引用,从而安全访问私有成员ob。同时注意set的构造方式,因为带状态的比较器无法默认构造,需显式传入实例。
完整代码实现
#include <map> #include <set> #include <string> struct MyStruct { unsigned p; unsigned t; }; class MyClass { // 内部定义带状态的比较器结构体 struct Compare { const MyClass& parent; // 必须通过构造函数传入MyClass实例引用 explicit Compare(const MyClass& ref) : parent(ref) {} bool operator()(const std::string& o1, const std::string& o2) const { // 从ob中获取对应的MyStruct,这里假设所有传入的键都存在于ob中 const MyStruct& s1 = parent.ob.at(o1); const MyStruct& s2 = parent.ob.at(o2); if (s1.p != s2.p) { return s1.p > s2.p; // 按p降序排序 } else { return s1.t < s2.t; // p相同时按t升序排序 } } }; public: void call() { // 先初始化ob的元素(修正原代码中的键名不一致问题) ob["o1_10"] = {10, 1}; ob["o2_20"] = {20, 2}; ob["o4_4"] = {4, 4}; ob["o5_10"] = {10, 4}; // 封装插入逻辑:避免直接用mbp[]默认构造set auto insert_to_mbp = [this](unsigned p_key, const std::string& str_key) { auto it = mbp.find(p_key); if (it == mbp.end()) { // 当p对应的set不存在时,显式传入比较器实例构造set it = mbp.emplace(p_key, Compare(*this)).first; } it->second.insert(str_key); }; // 执行插入操作 insert_to_mbp(ob["o1_10"].p, "o1_10"); insert_to_mbp(ob["o2_20"].p, "o2_20"); insert_to_mbp(ob["o4_4"].p, "o4_4"); insert_to_mbp(ob["o5_10"].p, "o5_10"); } private: std::map<unsigned, std::set<std::string, Compare>> mbp; std::map<std::string, MyStruct> ob; }; int main() { MyClass my_class; my_class.call(); return 0; }
关键细节说明
比较器设计:
Compare作为MyClass的内部结构体,持有MyClass的const引用,既能访问私有成员ob,又保证比较操作不会修改类实例状态。- 比较逻辑严格遵循需求:先按
p降序,p相等时按t升序。
set构造方式:
- 由于
Compare没有默认构造函数(必须传入MyClass引用),无法使用mbp[p_key]的默认构造行为,改用find+emplace组合,显式传递Compare(*this)构造set。
- 由于
键存在性处理:
- 示例中使用
ob.at()获取元素,若键不存在会抛出std::out_of_range异常。如果需要容错,可替换为ob.find()并添加键不存在的处理逻辑:bool operator()(const std::string& o1, const std::string& o2) const { auto it1 = parent.ob.find(o1); auto it2 = parent.ob.find(o2); // 假设键不存在时视为相等或按默认规则处理,可根据需求调整 if (it1 == parent.ob.end() || it2 == parent.ob.end()) return false; const MyStruct& s1 = it1->second; const MyStruct& s2 = it2->second; if (s1.p != s2.p) return s1.p > s2.p; return s1.t < s2.t; }
- 示例中使用
为什么之前的方案出错
- 类内成员函数:普通成员函数需要绑定
this指针才能作为可调用对象,但set的比较器类型要求固定,无法直接传递成员函数指针。 - 无状态仿函数:无法访问
MyClass的私有成员ob,除非破坏封装性将ob改为全局变量。 - bind方式:
std::bind返回的std::function可以作为比较器,但会带来额外的性能开销,且需要严格保证绑定的this指针生命周期,不如自定义带引用的仿函数高效。
内容的提问来源于stack exchange,提问作者Maryam Shabani
相关产品推荐
相关产品推荐

