C++中pair类型multiset的异常行为问题排查与解决
C++ multiset自定义排序的两个问题解答
嘿,我来帮你拆解这两个问题——它们本质上都是因为自定义比较器的逻辑不符合C++关联容器的等价性规则导致的,咱们一个个说:
问题1:为什么set无法同时存储{5,3}和{6,3}?
成因
C++的set(包括multiset)判断元素是否“等价”的规则是:如果comp(a,b)和comp(b,a)都返回false,那么a和b被视为等价元素。
你的Cmp结构体只比较了pair的第二个元素:
bool operator()(const pair_type& v1, const pair_type& v2) const { return v1.second < v2.second; }
对于{5,3}和{6,3},Cmp()({5,3}, {6,3})返回false(3不小于3),Cmp()({6,3}, {5,3})也返回false,所以容器会认为这两个元素是等价的。对于set来说,等价元素会被判定为重复,自然无法同时插入;而multiset虽然允许插入等价元素,但这会埋下问题2的隐患。
解决方法
修改比较器,当pair的第二个元素相等时,继续比较第一个元素,确保逻辑上不同的pair能被容器区分:
struct Cmp { bool operator()(const pair_type& v1, const pair_type& v2) const { // 先比较second,再比较first if (v1.second != v2.second) { return v1.second < v2.second; } return v1.first < v2.first; } };
这样,{5,3}和{6,3}会因为first不同,被判定为不等价元素,set就能同时存储它们,multiset的查找和删除也能精准定位了。
问题2:为什么find返回{5,3}的迭代器,erase删错了元素?
成因
multiset::find的查找逻辑是:返回第一个与目标元素等价的迭代器。如问题1所说,你的比较器让{5,3}和{6,3}等价,所以find({6,3})会找到容器中第一个插入的等价元素——也就是{5,3},调用erase(itr)自然会删除这个元素,而非你想要的{6,3}。
解决方法
有两种可行的方案,优先推荐第一种:
- 修改比较器(推荐):用问题1中的修正版比较器,让不同pair不再等价,此时
find({6,3})就能精准定位到目标元素,直接erase即可。 - 遍历等价元素范围:如果不想修改比较器,可以用
equal_range获取所有等价元素的迭代器范围,然后遍历找到目标元素再删除:
auto range = m.equal_range({6,3}); for (auto it = range.first; it != range.second; ++it) { if (it->first == 6 && it->second == 3) { m.erase(it); break; } }
不过这种方法需要遍历等价元素,效率不如修改比较器,只适合特殊场景下使用。
内容的提问来源于stack exchange,提问作者Hansie
相关产品推荐
相关产品推荐

