正在使用std::tuple来实现运算符<, = etc. efficient and correct?

问题描述 投票:0回答:3

我发现我最近经常使用 std::tuple 来实现排序、相等运算符,如下例所示。这是一个正确且有效的方法吗?当应在此类运算符中比较多个变量时,它特别有用。

#include <iostream>
#include <unordered_map>
#include <unordered_set>
#include <functional>
#include <tuple>

struct CustomerHash;
class Customer {
    std::string name;
    int age;
public:
    Customer(const std::string& name, int age) : name(name), age(age) {
    }
    
    bool operator<(const Customer& rop) const {
        return std::tuple<std::string, int>(name, age) < std::tuple<std::string, int>(rop.name, rop.age);
    }
    bool operator==(const Customer& rop) const {
        return std::tuple<std::string, int>(name, age) == std::tuple<std::string, int>(rop.name, rop.age);
    }
    
    friend struct CustomerHash;
};

struct CustomerHash {
    std::size_t operator()(const Customer& c) const {
        return std::hash<std::string>()(c.name) ^ c.age;
    }
};

int main(int argc, const char * argv[]) {
    std::unordered_set<Customer, CustomerHash> cmap;
    cmap.insert({"Mike", 40});
    cmap.insert({"Andy", 42});
    cmap.insert({"Jon", 40});
    std::cout << cmap.contains({"Andy", 42});
    return 0;
}
c++
3个回答
4
投票

这是正确的,但效率不高,因为

std::tuple
的构造函数会复制所有值。这也意味着您不能使用它来比较不可复制的类型。

您可以使用

std::tie
,它需要更少的代码,并且不会复制值,而是创建一个引用元组。

您的

operator<
将变成:

bool operator<(const Customer& rop) const {
        return std::tie(name, age) < std::tie(rop.name, rop.age);
}

4
投票

它绝对有效。它可能只是有点低效,因为 std::tuple 构造函数将复制参数。但如果这没有显示在您的绩效档案中,您可能只是不关心那里的绩效。

我会更加警惕:

std::hash<std::string>()(c.name) ^ c.age

这里,年龄只会影响散列的最后 9 位,因此可能分布得不太好。将

c.age
乘以某个大素数。但是,嘿,我可能很挑剔。


0
投票

使用 C++20,您现在可以简单地为结构实现 <=> 运算符。 这将允许按字典顺序比较结构。

来源:https://en.cppreference.com/w/cpp/language/operators#Comparison_operators

“如果定义了operator==,则编译器会自动生成不等运算符。同样,如果定义了三向比较运算符operator<=>,则编译器会自动生成四个关系运算符。operator==和operator!=,反过来,如果operator<=>被定义为默认值,则由编译器生成:“

struct Record
{
    std::string name;
    unsigned int floor;
    double weight;
 
    auto operator<=>(const Record&) const = default;
};
// records can now be compared with ==, !=, <, <=, >, and >=
std::set<Record> records; // Will now function as expected when inserting elements.

另一种选择是实现您自己的比较运算符。我们可以检查元组的实现< comparison operator and mimic it. Source:https://en.cppreference.com/w/cpp/utility/tuple/operator_cmp

"3-6) 按运算符按字典顺序比较 lhs 和 rhs <, that is, compares the first elements, if they are equivalent, compares the second elements, if those are equivalent, compares the third elements, and so on. 3) For empty tuples, returns false. For non-empty tuples, the effect is equivalent to"

if (std::get<0>(lhs) < std::get<0>(rhs)) return true;
if (std::get<0>(rhs) < std::get<0>(lhs)) return false;
if (std::get<1>(lhs) < std::get<1>(rhs)) return true;
if (std::get<1>(rhs) < std::get<1>(lhs)) return false;
...
return std::get<N - 1>(lhs) < std::get<N - 1>(rhs);

以下是此类实现的示例:

来源:https://www.pluralsight.com/blog/software-development/simplifying-lexicographyal-comparisons-with-c--

friend bool operator<(const Time& lhs, const Time& rhs) {

//
// First compare by hours
//
if (lhs.m_hours < rhs.m_hours) {
  return true;
}
if (lhs.m_hours > rhs.m_hours) {
  return false;
}

//
// Same hours, compare by minutes
//
if (lhs.m_minutes < rhs.m_minutes) {
  return true;
}
if (lhs.m_minutes > rhs.m_minutes) {
  return false;
}

//
// Same hours and minutes, compare by seconds
// (left as an exercise for the reader...)
//

}

最后一个选项是将 std::tie 与临时引用元组一起使用。 在这种情况下,您仍然会实例化“某些东西”。请参阅 Lukas-T 的回答。

© www.soinside.com 2019 - 2024. All rights reserved.