【问题标题】:Functors vs comparators函子与比较器
【发布时间】:2019-07-12 06:57:33
【问题描述】:

我知道仿函数是一个函数对象,是结构定义中 () 运算符的重载。 此外,在算法中使用仿函数似乎很简单,只需调用此例程即可。

但是我无法理解比较器。为什么首先在模板参数中使用它们。

有人可以详细说明两者之间的区别,并在 STL 中使用类似 map 的模板实现

编辑:

我正在寻找以下具体的答案

  1. 为什么需要比较器而不是函数对象(比较器也更常见于容器中?)
  2. 传递比较器而不是函数对象的可能实现(非 STL,即在 C++ 代码中)

【问题讨论】:

  • 你传递的是一个类型,而不是一个对象,所以一个实体可以创建一个该类型的函子,然后它的行为就好像你使用了一个函子
  • 好吧,稍微思考一下让我觉得它与内存分配有关,但如果有人能提供一个实现,那就太好了
  • 函子是对象——它们确实会占用内存。除了您提供对象本身或类型以便以后可以创建对象之外,实际上没有什么区别。
  • “比较器”是比较的函数或函数对象,仅此而已。现在,C++ 有一个绝招。如果您将类型而不是对象传递给函数模板,并让模板创建所需的对象,则可以更轻松地优化整个事物。为此,您需要该类型的所有对象都具有相同的行为。该技术用于各种函数对象,而不仅仅是比较器。
  • @Fureeish Functor 是数学分支中的一个术语,称为范畴论,请查阅。事实证明,这个分支在编程中也很重要。

标签: c++ c++11 comparator functor


【解决方案1】:

函子的定义你是对的 - 虽然这个词在语言标准本身中并不存在,所以人们使用它的方式可能会有一些细微的变化。

标准库中有许多函数或类模板将采用某种可调用对象 - 这可能是函子或指向函数的指针(实际上只是一个函数,而不是带有 operator() 的类)。

比较器是一个符合Compare requirements的类型的对象——也就是说,一个函数或类对象可以用两个东西调用并返回一个bool,特别是满足称为严格弱排序的一些数学要求。

本质上,这意味着比较器是一个函子,您可以使用它来将一些数字按正确的顺序排列。 (数字、std::strings、Customers 等等,只要有一种合理一致的方式将它们按顺序排列即可。

所以一个使用仿函数的简单例子可能是:

void print(int i)
{
    std::cout << i << '\n';
}
// ...
std::for_each(std::begin(some_ints), std::end(some_ints), print);

但如果您想按客户 ID 对一些 Customers 进行排序,您可以这样做:

struct Customer {
    std::string surname;
    std::string given_name;
    std::uint64_t customer_id;
};
bool compareById(Customer const& first, Customer const& second)
    // this function meets the Compare requirements
{
    return first.customer_id < second.customer_id;
}
// ...
std::sort(std::begin(customers), std::end(customers), compareById);

假设您稍后想按客户的姓名对客户进行排序 - 首先是姓氏,然后是名字,如果姓氏相同,您可以提供不同的功能:

bool compareByName(Customer const& first, Customer const& second)
{
    // std::tie is an idiomatic way to correctly sort on multiple values
    return std::tie(first.surname, first.given_name)
                < std::tie(second.surname, second.given_name);
}
std::sort(std::begin(customers), std::end(customers), compareByName);

我正在努力发明一个示例,您需要将比较器作为一个类,但是假设您想将它对日志文件所做的所有比较打印出来;那么该文件需要由对象进行状态存储:

struct LoggingCustomerComparator {
    std::ostream& logFile;
    LoggingCustomerComparator(std::ostream& logFile) : logFile(logFile) {}
    bool operator()(Customer const& first, Customer const& second)
    {
        // assume we have an operator<< for Customer
        logFile << "Comparing: " << first << " and " << second << '\n';
        return first.customer_id < second.customer_id;
    }
};
// ...
using OrderId = std::uint64_t;
using LCC = LoggingCustomerComparator;
std::map<Customer, OrderId, LCC> latestCustomerOrder(LCC(std::clog));
//                          ^^^ type                 ^^^ construct object with the log file we want

上面说明了如何使用仿函数或比较器的函数模板,但是如果你想编写这样的函数模板呢?让我们以标准库算法的风格实现Bogosort

template <typename RandIt, typename Comp>
void bogosort(RandIt first, RandIt last, Comp comp)
{
    std::random_device rd;
    std::mt19937 g(rd());

    while ( !std::is_sorted(first, last, comp) ) {
        std::shuffle(first, last, g);
    }
}

查看is_sorted 可能如何实现see here

【讨论】:

  • 好答案 :) 要进一步扩展,您可以使用另一个功能 bool compareBySurname(Customer const&amp; first, Customer const&amp; second) { return first.surname &lt; second.surname; }
  • 您的回答详细说明了函子,但您能否提供一个模板需要比较器类型的类型示例(例如 C++ 代码中 STL 模板的可能实现)
【解决方案2】:

在评论部分,一位用户指出了正确的答案,回头我只是在扩展它

基本上比较器是类型而不是对象,因此传递类型对模板类更有意义,因为它的元编程,即编译器正在编写自己的代码,所以类自己创建内部对象更有意义而不是依靠用户来提供。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-12-06
    • 2022-11-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-04-25
    • 1970-01-01
    相关资源
    最近更新 更多