自定义比较对象始终返回相同结果时,C++标准容器(如std::set/std::map)的预期行为是什么?
这个问题问到了C有序容器的一个关键规则——当你给std::set、std::map这类依赖排序的容器用了一个始终返回相同结果的比较谓词(不管一直是true还是false),这直接违反了C标准对这类容器的核心要求,最终结果是未定义行为(Undefined Behavior)。咱们结合标准规则和你给出的例子来详细拆解:
首先明确C++标准的硬性要求
std::set和std::map的比较谓词必须满足**严格弱序(Strict Weak Ordering)**的规则,这是容器能正确工作的基础。严格弱序需要满足以下几个核心条件:
- 自反性:对任意元素
x,comp(x, x)必须返回false(不能判定元素自己比自己"大"或"小") - 非对称性:如果
comp(x, y)为true,那么comp(y, x)必须为false - 传递性:如果
comp(x, y)和comp(y, z)都为true,那么comp(x, z)也必须为true - 等价传递性:如果
x和y等价(即!comp(x,y) && !comp(y,x)),y和z等价,那么x和z也必须等价
你的例子违反了哪些规则?
你定义的operator>被硬编码为始终返回false,然后用greater<A>作为std::set的比较谓词(greater会调用这个重载的operator>)。咱们对应规则看:
- 自反性:
comp(a,a)返回false,这一条是满足的; - 但对于任意两个不同的
A对象a和b,comp(a,b)和comp(b,a)都返回false,这直接违反了严格弱序的非对称性和等价逻辑——容器会认为所有元素都是等价的,因为!comp(a,b) && !comp(b,a)始终为true。
你看到的g++-10行为只是未定义行为的一种表现
你测试时发现set只保留了第一个元素,这只是编译器实现的一种可能结果,而非标准规定的预期行为。为什么会这样?因为std::set插入元素时,会用比较谓词查找是否存在等价元素——当所有元素都被判定为等价时,后续的插入请求都会被容器拒绝。
但请记住:未定义行为意味着标准不对任何结果负责。换成MSVC、clang或者不同版本的g++,完全可能出现其他情况:比如程序崩溃、错误地插入所有元素、甚至出现逻辑混乱的元素顺序。你不能依赖当前g++的表现来编写代码。
正确的做法是什么?
解决这类问题的核心是让比较谓词严格遵守严格弱序。比如针对你的A结构体,正确的operator>应该这样写:
bool operator > (const A& first, const A& second) { // 先比较int成员,再比较double成员,确保有明确的排序逻辑 if (first.i_mem != second.i_mem) { return first.i_mem > second.i_mem; } return first.d_mem > second.d_mem; }
这样的谓词能正确区分元素的大小关系,满足严格弱序的所有要求,容器的行为也会完全符合标准预期。
内容的提问来源于stack exchange,提问作者Dinesh Maurya

