高效简单的结构比较运算符

时间:2018-09-05 07:59:51

标签: c++

我正在处理的应用程序当前具有大量结构,其中包含从各种来源(如数据库和文件)输入的数据。例如这样的

struct A 
{
    float val1;
    std::string val2;
    int val3;

    bool operator < (const A& other) const;
};

为了进行处理,这些结构存储在STL容器(例如地图)中,因此需要比较运算符。这些都是相同的,并且可以使用简单的布尔逻辑将它们编写为:

bool A:operator < (const A& o) const {
    return val1 < o.val1 || 
        (val1 == o.val1 && ( val2 < o.val2 || 
            (val2 == o.val2 && ( val3 < o.val3 ) ) );
}

这似乎很有效,但是有几个缺点:

  1. 如果结构是一打或更多的成员,这些表达式将变得巨大。
  2. 如果成员发生更改,则很难编写和维护。
  3. 需要对每个结构分别进行操作。

是否有更可维护的方式来比较这样的结构?

2 个答案:

答案 0 :(得分:24)

您可以像这样使用<tuple>附带的内置比较:

#include <tuple>

bool A::operator < (const A& rhs) const {
    return std::tie(val1, val2, val3) < std::tie(rhs.val1, rhs.val2, rhs.val3);
}

当越来越多的数据成员添加到结构中时,这不会扩展,但这也可能暗示您可以创建实现operator <的中间结构,因此可以很好地与上面的a实现一起使用顶级operator <

让我在operator <上添加另外三个评论。

  1. 一旦有了operator <,客户就会期望也提供所有其他比较运算符。在C ++ 20中进行三向比较之前,您可以通过以下方式避免不必要的样板代码:使用Boost运算符库:

    #include <boost/operators.hpp>
    
    struct A : private boost::totally_ordered<A> { /* ... */ };
    

    为您生成基于operator <operator ==的所有运算符。

  2. 在您的示例中,操作员无需成为A的成员。您可以将其设置为自由函数,这是更好的选择(有关原理,请参见here)。

  3. 如果没有与A相关的内在顺序,而您只需要operator <将实例作为键存储在std::map中,请考虑提供一个命名谓词。

答案 1 :(得分:9)

lubgr的好答案。

我执行的另一项改进是在要由其成员排序的任何对象上创建成员函数as_tuple

#include <string>
#include <tuple>
#include <iostream>

struct A 
{
    float val1;
    std::string val2;
    int val3;

    // provide easy conversion to tuple
    auto as_tuple() const
    {
        return std::tie(val1, val2, val3);
    }
};

经常引起人们对比较对象和元组互换性的一般系统的思考

template<class T> auto as_tuple(T&& l) -> decltype(l.as_tuple()) 
{
    return l.as_tuple();
}

template<class...Ts> 
auto as_tuple(std::tuple<Ts...> const& tup) 
-> decltype(auto)
{
    return tup;
}

template<class L, class R>
auto operator < (L const& l, R const& r)
-> decltype(as_tuple(l), void(), as_tuple(r), void(), bool())
{
    return as_tuple(l) < as_tuple(r);
}

其中允许使用以下代码:

int main()
{
    auto a = A { 1.1, "foo", 0 };
    auto b = A { 1.1, "foo", 1 };

    auto test1 = a < b;
    std::cout << test1 << std::endl;

    auto test2 = a < std::make_tuple(1.1, "bar", 0);
    std::cout << test2 << std::endl;

    auto test3 = std::make_tuple(1.0, "bar", 0) < std::make_tuple(1.1, "bar", 0);
    std::cout << test3 << std::endl;

    auto test4 = a < std::make_tuple(2l, std::string("bar"), 0);
    std::cout << test4 << std::endl;

}

示例:http://coliru.stacked-crooked.com/a/ead750f3f65e3ee9