std::map自定义key,非严格弱序导致"invalid comparator"异常
std::map自定义key的方法是重写operator<(),但是如果没有严格弱序,极有可能导致”invalid comparator”异常,也就是提示比较器无效,如下demo代码:
class MyKey { public: MyKey(int num1, int num2) : num1_(num1) , num2_(num2) { } bool operator<(const MyKey& key) const { return num1_ < key.num1_ ? true : num2_ < key.num2_; } int num1_; int num2_; }; int main() { std::map<MyKey, int> map1; map1[MyKey(1, 2)] = 12; map1[MyKey(2, 1)] = 21; system("pause"); return 0; }
要搞清楚为什么会出现这个异常并解决,要先了解一下什么是严格弱序和operator<()是如何被std::map使用的。
什么是严格弱序?
严格弱序有三个特性:
非对称性:两个关键字不能同时“严格弱序”于对方
传递性:如果a“严格弱序”于b,且b“严格弱序”于c,则a必须“严格弱序”于c
非自反性:如果存在两个关键字,任何一个都不“严格弱序”于另一个,则这两个关键字是相等的
通常我们认为”<”就是严格弱序,而”<=”是非严格弱序的,基于这个来看,把条件中的”严格弱序”替换成”<”,我们会发现严格弱序的三个特性是必然的,而反过来验证”<=”则是不成立的。
比较表示为:
a 小于 b:
a < b
a 大于 b:
b < a
a 等于 b:
!(a < b) && !(b < a)
operator<()如何被std::map使用的?
假设已经有一个为key1的元素,当我们需要插入一个key2的元素时:
通过单步调试代码的方式可以发现,operator<()被调用了两次,第一次是key1.operator<(key2),第二次是key2..operator<(key1)
那么问题来了,std::map的key只需要”<”比较,它是如何比较两个key相等的?这大概就是operator<()需要被调用两次的一个原因,当key1<key2不成立,而key2<key1也不成立时,key1和key2只能相等的,这就是严格弱序的非自反性。
比较条件表示如下:
key1小于key2:key1 < key2
key1大于key2:key2 < key1
key1等于key2:!(key1 < key2) && !(key2 < key1)
“invalid comparator”异常为何出现?
了解了上面的知识点后,我们以开头的例子分析为什么会出现”invalid comparator”异常,两个key分别为:key1(1,2)和key2(2,1),比较函数为:
bool operator<(const MyKey& key) const { return num1_ < key.num1_ ? true : num2_ < key.num2_; }
我们可以得到:
key1小于key2 | key1大于key2 |
---|---|
key1 < key2 | key2 < key1 |
true | true |
可以看到,key1既大于key2又小于key2,这完全就是两个自相矛盾的条件,这就是std::map报无效比较器的原因。那么比较器正确的写法是什么呢?
比较器operator<()的正确写法
依然以开头demo为例修改operator<()如下:
bool operator<(const MyKey& key) const { if (num1_ != key.num1_) return num1_ < key.num1_; if (num2_ != key.num2_) return num2_ < key.num2_; return false; }