如果比较函数不是运算符 <,为啥 std::sort 会崩溃?

Posted

技术标签:

【中文标题】如果比较函数不是运算符 <,为啥 std::sort 会崩溃?【英文标题】:Why will std::sort crash if the comparison function is not as operator <?如果比较函数不是运算符 <,为什么 std::sort 会崩溃? 【发布时间】:2021-11-19 13:42:45 【问题描述】:

以下程序是用VC++ 2012编译的。

#include <algorithm>

struct A

    A()
        : a()
    

    bool operator <(const A& other) const
    
        return a <= other.a;
    

    int a;
;

int main()

    A coll[8];
    std::sort(&coll[0], &coll[8]); // Crash!!!

如果我将return a &lt;= other.a; 更改为return a &lt; other.a;,那么程序将按预期运行,没有任何异常。

为什么?

【问题讨论】:

std::sort 的比较器需要严格的弱排序,&lt;=提供。 你应该为 A ctor 写 a(0)...但它不会在这里崩溃! @LaszloPapp:是的。它对a() 进行值初始化(这就是a() 的含义),对于int 而言,这意味着0。 @LaszloPapp: ***.com/questions/14259602/… 请将未定义行为 &coll[8] 替换为 coll + 8 【参考方案1】:

std::sort 需要一个满足 严格弱排序 规则的排序器,对此进行了解释 here

所以,你的比较器说 a &lt; ba == b 不遵循 严格弱排序 规则,算法可能会崩溃,因为它会进入无限循环。

【讨论】:

+1 这很好地说明了std::sort 的比较器的要求。如果您的编译器与 C++11 兼容(并且 OP is 兼容;VS2012),我将使用 std::sort(std::begin(coll), std::end(coll)); 来遵循这一点。如果您曾经更改数组的尺寸,或者改用任何标准容器,您会很高兴的。 进入无限循环只会导致它卡住。相反,它转储核心。为什么? @btilly 我认为是因为std::sort 使用递归算法,在无限循环的情况下会导致堆栈溢出。 您必须详细了解它检查的内容,但标准的排序例程旨在运行得非常非常快,因此它们不会检查您所做的一切以查看是否正常,他们只是依靠它。如果你的比较返回了不可能的结果,那么不可能的事情就会发生——比如说它得到了一些比较的结果,并且它使用它作为查找位置的索引,只有它“知道”哪些值是可能的并且“知道”结果引用将在有效存储中,因此它只是获取它。 Kaboom:SIGSEGV 运气好。如果运气不好,它会默默地处理您的数据。【参考方案2】:

xorguy 的答案还不错。

我只想从标准中添加一些引用:

25.4 排序及相关操作[alg.sorting]

为了使 25.4.3 中描述的算法以外的算法正常工作,comp 必须对值引入 严格的弱排序

术语 strict 指的是非自反关系的要求(!comp(x, x) 对于所有 x),术语 weak 指的是非自反关系的要求与全排序一样强,但比部分排序强。

所以 xorguy 解释得很好:你 comp 函数说 a &lt; ba == b 不遵循 严格弱排序 规则...

【讨论】:

【参考方案3】:

您的代码的问题是您正在访问无效的内存。代码

coll[8]

试图访问最后一个数组元素之后的元素(最后一个元素索引为 7)。 我建议使用 std::array 代替纯 C 数组。

std::array<A, 8> a;

// fill it somehow

std::sort(a.begin(), a.end());

【讨论】:

以上是关于如果比较函数不是运算符 <,为啥 std::sort 会崩溃?的主要内容,如果未能解决你的问题,请参考以下文章

为啥 `std::initializer_list` 不提供下标运算符?

为啥 std::apply 可以调用 lambda 而不是等效的模板函数?

为啥 std::fstream 返回 void 而不是 bool

为啥 std::compare_three_way 不是模板结构/函子

为啥我不能在 std::array< int, 3 > 的指针上使用运算符 [] 来索引值?

为啥 std::optional 对 std::nullopt 类型的操作数有一个特殊的相等运算符