同事在工作中与到的一个问题,组装资源的时候,往std::set
里插入自定义结构体失败,导致使用时查找失败。几个人一起排查了一下,最后发现是对自定义结构operator<
操作符进行重载时有歧义,导致了线上问题。
错误示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Range {public : int min; int max; bool operator <(Range const & b) const { if (min == b.min && max == b.max) { return false ; } else { return min < b.min; } } Range(int min, int max) { this ->min = min; this ->max = max; } };
std::set
是去重的,那么如何判断两个元素是否相同?这就依赖operator<
,对于a
、b
两个元素,分别调用a<b
以及b<a
,如果两个都返回false
,那么就判断a=b
。这种判断机制,也是造成如上定义会出现问题的关键。
执行代码:
1 2 3 4 5 6 7 8 9 10 11 12 int main () { set <Range> range_set; Range r1 (1 , 2 ) ; Range r2 (1 , 2 ) ; Range r3 (1 , 1 ) ; range_set.insert(r1); range_set.insert(r2); range_set.insert(r3); for (auto &r : range_set) { cout << "[" << r.min << ", " << r.max << "]" << endl ; } }
输出:
可以看到r1
和r2
的确是相同的,只能插入其中一个,这个符合预期;r1
和r3
是两个不同的区间,但是r3
的插入失败了。这里就是由于在Range
中定义的operator<
仅仅对区间的左边界进行来判断。return min < b.min;
这个判断,对于a<b
和b<a
都是返回false
,此时程序就会认为a
和b
是相等的,导致其中一个插入失败。
单独比较一下r1
跟r3
:
1 2 cout << "r1 < r3: " << (r1 < r3) << endl ; cout << "r3 < r1: " << (r3 < r1) << endl ;
可以发现两次判断都是false
,这样就会被set
判断为是相等的两个元素。
正确示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class Range {public : int min; int max; bool operator <(Range const & b) const { if (min == b.min && max == b.max) { return false ; } else if (min < b.min) { return true ; } else if (min == b.min) { return max < b.max; } else { return false ; } } Range(int min, int max) { this ->min = min; this ->max = max; } };
执行代码:
1 2 3 4 5 6 7 8 9 10 11 12 int main () { set <Range> range_set; Range r1 (1 , 2 ) ; Range r2 (1 , 2 ) ; Range r3 (1 , 1 ) ; range_set.insert(r1); range_set.insert(r2); range_set.insert(r3); for (auto &r : range_set) { cout << "[" << r.min << ", " << r.max << "]" << endl ; } }
输出:
输出符合预期。
这个问题其实在UT阶段就应该被发现,这里还是因为项目上线比较紧,而导致很多质量把控环节都疏忽了。