findingsea's Studio.

C++ 结构体操作符重载

Word count: 665 / Reading time: 3 min
2019/07/05 Share

同事在工作中与到的一个问题,组装资源的时候,往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;

// 错误示例:a.min=b.min,那么a<(b)的判断为false,b<(a)的判断为false,就会判断a=b,无法插入
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<,对于ab两个元素,分别调用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;
}
}

输出:

1
[1, 2]

可以看到r1r2的确是相同的,只能插入其中一个,这个符合预期;r1r3是两个不同的区间,但是r3的插入失败了。这里就是由于在Range中定义的operator<仅仅对区间的左边界进行来判断。return min < b.min;这个判断,对于a<bb<a都是返回false,此时程序就会认为ab是相等的,导致其中一个插入失败。

单独比较一下r1r3

1
2
cout << "r1 < r3: " << (r1 < r3) << endl;  // 0
cout << "r3 < r1: " << (r3 < r1) << endl; // 0

可以发现两次判断都是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;
}
}

输出:

1
2
[1, 2]
[1, 1]

输出符合预期。

这个问题其实在UT阶段就应该被发现,这里还是因为项目上线比较紧,而导致很多质量把控环节都疏忽了。

CATALOG
  1. 1. 错误示例
  2. 2. 正确示例