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



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

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. 它需要为每个结构单独完成。

有没有更易于维护的方法来比较这样的结构?

您可以使用<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 <的中间结构,从而很好地与顶级operator <的上述实现配合使用。

让我对operator <补充三点评论。

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

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

    它根据operator <operator ==为您生成所有运算符。

  2. 在您的示例中,运算符不需要是A的成员。你可以把它变成一个自由函数,这是更可取的(见这里的基本原理(。

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

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

最新更新