同事在工作中与到的一个问题,组装资源的时候,往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阶段就应该被发现,这里还是因为项目上线比较紧,而导致很多质量把控环节都疏忽了。