【问题标题】:std::sort with custom comparatorstd::sort 与自定义比较器
【发布时间】:2019-05-17 09:01:03
【问题描述】:

在下面的代码中,为什么IntComparator()IntComparator2IntComparator3 这三个参数都作为sort() 函数的第三个参数工作?他们不会有不同的左值函数类型吗?基于https://en.cppreference.com/w/cpp/algorithm/sort 它说

比较函数的签名应该等同于 以下:

bool cmp(const Type1 &a, const Type2 &b);

似乎与IntComparator2 更匹配?

还有哪一个更可取?第三个选项似乎更简单、更直观。


#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>

struct IntComparator
{
  bool operator()(const int &a, const int &b) const
  {
    return a < b;
  }
};

bool IntComparator2 (const int &a, const int &b)
{
    return a < b;
}

bool IntComparator3 (int a, int b)
{
    return a < b;
}

int main()
{
    int items[] = { 4, 3, 1, 2 };
    std::sort(items, items+4, IntComparator());

    for (int n=0; n<4; n++) {
        std::cout << items[n] << ", ";
    }

    std::cout << "\n";

    int items2[] = { 4, 3, 1, 2 };
    std::sort(items2, items2+4, IntComparator2);

    for (int n=0; n<4; n++) {
        std::cout << items2[n] << ", ";
    }

    std::cout << "\n";

    int items3[] = { 4, 3, 1, 2 };
    std::sort(items3, items3+4, IntComparator3);

    for (int n=0; n<4; n++) {
        std::cout << items3[n] << ", ";
    }

    std::cout << "\n";

    return 0;
}

【问题讨论】:

  • 我只是在阅读上面给出的参考资料; 应该等价于的表述确实有些模棱两可。
  • @Codor 标准说“假定comp 不会通过取消引用的迭代器应用任何非常量函数。”您也许可以使用bool IntComparator3 (int &amp;a, int &amp;b),但如果ab 被它修改,这是未定义的行为
  • @Caleth 我们最近禁止了那个。

标签: c++ sorting


【解决方案1】:

std::sort 接受functor。这是可以调用的任何对象(使用正确的参数)。该函数通过使用模板来实现这一点,如下所示

template<typename Iter, typename Comp>
void sort(Iter begin, Iter end, Comp compare) { ... }

IntComparator1、2 和 3 都是此比较器的有效函子,因为它们都可以使用带有 2 个整数的 operator() 调用。

也如你所说,第三个选项确实通常更直观。

【讨论】:

  • Lambda 是另一种选择,可能更直观。
  • 对,暂时忘记了这些:D Lambdas 通常确实更好。
  • 只要它们保持简短并且相对不言自明(并假设它们在代码的其他地方没有用处)
  • 第一个(函子)和一个lamba,也获得了一个显着的好处,即在std::sort算法实现中内联的可能性非常高中,其他的二、两者都有效地指向函数,都不太可能实现。它可以在性能上产生巨大的差异。仅出于这个原因,仿函数或 lambda 就应该在优先级列表中居高不下。
  • 从技术上讲,函数不是对象,但它们会衰减为指向函数的指针。
【解决方案2】:

sort() 函数只会在需要比较时调用您提供的比较器函数。比较器获取其参数(值、引用、const ref)的方式不是sort() 关心的问题,无论您的比较器如何在内部获取其参数,它都会以相同的方式调用它(传递相同类型的两个参数)。
这在比较器定义之外是难以察觉的,因为我们称您为三个函数的方式完全相同。

唯一要求的是比较器只接受两个参数,并且它们必须与要排序的元素的类型相同。

但是,通过 const ref 传递更好,因为您的比较器保证不会修改它比较的参数,并且它还避免了无用的副本(性能增益)。这就是为什么他们写了should be equivalent(这不同于must be equivalent)。

【讨论】:

  • 你漏掉一个细节,不允许比较器修改参数,否则sort做不到。这并不意味着参数不能作为非常量引用传递,但如果是,那么比较器不得修改它们
  • @foreknownas_463035818 当然可以!你是完全正确的。但是如果用户给出了修改参数的比较器,那完全是用户的错,sort()对这样的错误概不负责。
  • 我指的是“唯一需要的是比较器......”我不完全同意,这是sort 的责任来说明先决条件是什么,以及不修改其参数的比较器是一个
【解决方案3】:

它们是等价的,因为 C++ 规范说它们都符合二元谓词的要求。以下摘录似乎相关。

[function.objects]

20.14.1 函数对象类型是一种对象类型,它可以是函数调用 ([expr.call], [over.match.call]) 中后缀表达式的类型。224 函数对象是函数对象类型的对象。 在人们期望将指向函数的指针传递给算法模板的地方,接口被指定为接受函数对象。这不仅使算法模板能够与指向函数的指针一起工作,而且还使它们能够与任意函数对象一起工作。

[alg.sorting]

25.7.2 Compare 是一种函数对象类型 ([function.objects]),它满足名为 BinaryPredicate ([algorithms.requirements]) 的模板参数的要求。应用于比较类型对象的函数调用操作的返回值,当上下文转换为 bool ([conv]) 时,如果调用的第一个参数小于第二个参数,则返回 true,否则返回 false。 Compare comp 用于假设排序关系的算法。

[algorithms.requirements]

25.2.8 在没有其他约束的情况下,只要算法需要一个函数对象,当应用于解引用两个相应迭代器的结果或解引用迭代器和类型 T(当 T 是签名的一部分时)返回时,就会使用 BinaryPredicate 参数可测试为真的值。换句话说,如果一个算法将 BinaryPredicate binary_pred 作为其参数,并将 first1 和 first2 作为其迭代器参数,具有各自的值类型 T1 和 T2,它应该在上下文转换为 bool ([转换])。除非另有说明,否则 BinaryPredicate 始终将第一个迭代器的 value_type 作为其第一个参数,也就是说,在 T value 是签名的一部分的情况下,它应该在构造 binary_pred(*first1, value) 上下文转换为 bool ( [转换])。 binary_pred 不应通过取消引用的迭代器应用任何非常量函数。 给定一个类型为(可能为 const)T1 的泛左值 u 指定与 *first1 相同的对象,以及一个类型为(可能为 const)T2 的泛左值 v 指定与 *first2 相同的对象,binary_pred(u, *first2) 、binary_pred(*first1, v) 和 binary_pred(u, v) 均应为等于 binary_pred(*first1, *first2) 的有效表达式,且 binary_pred(u, value) 应为有效等于 binary_pred(*first1, value) 的表达式。

至于哪个更可取的问题,我会说这是基于意见的,除非在您的具体情况下,分析表明一个更好。

【讨论】:

    猜你喜欢
    • 2020-10-02
    • 1970-01-01
    • 1970-01-01
    • 2013-12-01
    • 2021-01-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多