【问题标题】:operator< comparing multiple fields运算符<比较多个字段
【发布时间】:2020-01-18 10:38:15
【问题描述】:

我有以下运算符

    inline bool operator < (const obj& a, const obj& b) 
    {
        if(a.field1< b.field1)
            return true;
        else
            return a.field2 < b.field2;
    }

我觉得这是不正确的,如果不对成员变量进行第三次比较测试,您将无法做到这一点,但我找不到任何这不起作用的示例。 那么这真的会像预期的那样排序吗? 谢谢

编辑: 我会把它编码为:

    inline bool operator < (const obj& a, const obj& b) 
    {
        if(a.field1< b.field1)
            return true;
                    else if(a.field1> b.field1)
            return false;
        else
            return a.field2 < b.field2;
    }

有什么不同吗?我问是因为我知道我的经验是正确的,但也比第一个更长

【问题讨论】:

  • 为什么它不能工作?也许您应该更好地解释您的感受以及“按预期排序”是什么。
  • 您的原件以if( obj(2,1) &lt; obj(1,2) ) 失败。
  • 那么这就是严格的弱排序吗?如果我至少确定没有运行时错误,我会很好的
  • 这对我来说是一个相当普遍的习惯用法 - 检查除了最后一个成员之外的每个成员 &lt; 然后是 &gt;,仅在以后继续检查 == 案例。然后对于你不关心==&gt; 的最后一个字段,所以只需返回&lt; 结果。就个人而言,我会将每个 if 语句写在一行上,而不会打扰 else,因为 return 无论如何都会退出该函数。
  • @Rob - 我错过了什么?据我所知,该功能作为非会员应该没问题。字段比较是否有效仅取决于字段类型是否定义了 operator。实际上-更正我之前的评论-我通常会检查(a.f1 &lt; b.f1)然后(b.f1 &lt; a.f1)。 IIRC 当您从标准库中定义 operator&lt; 时,您会得到一个自动的 operator&gt;,但即使由于某种原因失败,这也是可以的。

标签: c++ comparison-operators lexicographic


【解决方案1】:

我想自己做这一切..

只有当Obj::field1 的值相等时,才应该比较Obj::field2 的值。

通俗易懂的方式:

/* This will meet the requirements of Strict-Weak-Ordering */

if (a.field1 != b.field1) return a.field1 < b.field1;
else                      return a.field2 < b.field2;

正确(推荐)方式:

正确”的实现方式只使用operator&lt;比较字段,下面看起来比实际复杂。

然而,它会产生与之前编写的易于理解示例相同的结果。

return a.field1 < b.field1 || (
  !(b.field1 < a.field1) && a.field2 < b.field2
);

一定有一种方法可以实现operator&lt;而不引起很多头痛?

C++11

您可以使用 STL 中的std::tuple 来定义多个字段,例如在下面的示例中。

#include <utility>

...

inline bool
operator< (Obj const& lhs, Obj const& rhs)
{
  return std::tie (lhs.field1, lhs.field2) < std::tie (rhs.field1, rhs.field);
}

C++03

如果您的编译器尚不支持 C++11,而您只需比较每个对象的两个字段,则可以改用 std::pair

std::make_pair 的原因与前面使用std::tie 的示例相同。

#include <utility>

...

inline bool
operator< (Obj const& lhs, Obj const& rhs)
{
  return std::make_pair (lhs.field1, lhs.field2)
       < std::make_pair (rhs.field1, rhs.field2);
}

使用std::pair 将需要创建成员的副本,这在某些情况下是不可取的。

这真的是推荐的练习吗?

请参阅以下问题/答案以获取更多信息,但要总结一下; c++11 的方法不会造成太大的开销,而且实现起来非常简单。

【讨论】:

  • return (a.field1 == b.field1) ? a.field2 &lt; b.field2 : a.field1 &lt; b.field1;
  • 谢谢。这是否意味着第一个版本是 std::map 中键的不正确顺序? (基本上不是严格的弱排序,会导致运行时错误)还是只是与我预期的排序不同?
  • @lezebulon:这不是 SWO,请参阅我回答中的示例。
  • @lezebulon 您的原始版本不符合排序功能的要求,因此您的代码具有未定义的行为。任何事情都有可能发生,而且实际上会发生很多奇怪的事情。
  • 写这个条件的“规范”方式是return a.field1 &lt; b.field1 || (!(b.field1 &lt; a.field1) &amp;&amp; a.field2 &lt; b.field2)。规范主要是因为它只使用&lt;;在实践中,我觉得它不是很清楚,并且只会在库模板的上下文中使用它(我不想对标准库所需的类型以外的类型施加任何额外的约束)。
【解决方案2】:

想想如果a.field1 大于b.field1a.field2 小于b.field2 会发生什么。在这种情况下,您根据field2 进行比较,这不是您想要的。

您只想在field1 字段相等的情况下使用field2,因此您正在寻找类似(伪代码)的东西:

if a.field1 < b.field1: return true
if a.field1 > b.field1: return false
# field1s is equal here.
return a.field2 < b.field2

【讨论】:

    【解决方案3】:

    没有。你还需要抓住(a.field1 &gt; b.field1)

    这不是严格的弱排序,因为它会给出(1,2) &lt; (2,1),但也会给出(2,1) &lt; (1,2)

    【讨论】:

      【解决方案4】:

      这是一个依赖逻辑短路规则来避免显式分支的版本

      template<typename T>
      bool operator< (T const& a, T const& b)
      {
              return (
                       ( a.field1 < b.field1 ) || (( a.field1 == b.field1 ) &&
                       ( a.field2 < b.field2 ))
              );
      }
      

      这假设您的原始类型 field1 有一个 operator==。为超过 2 个字段键入此内容会变得乏味,但如果您的类 obj 将字段存储在 std::array&lt;T, N&gt; 中,对于某些类型 T 和大小 N,您可以使用 std::lexicographical_compare

      template<typename T, int N>
      struct obj
      {
          std::array<T, N> field;
      };
      
      bool operator< (obj const& a, T const& b)
      {
              return std::lexicographical_compare(
                  a.field.begin(), a.field.end(), 
                  b.field.begin(), b.field.end()
              );
      }
      

      请注意,有一篇草稿 N3326 讨论了为类类型自动添加运算符 ==&lt;

      【讨论】:

      • 为什么要避免显式分支?我不认为它比逻辑短路慢。
      • @MooingDuck 还没有实际测试过,甚至可能因平台而异。只是出于好奇而写的。
      【解决方案5】:

      下面描述的我的方法涉及一些宏,但在许多情况下仍然有用。也许这样的事情也可以用内联函数来完成。

      #define CMP_LT2(a, b) ((a) < (b) ? (a) : (b))
      #define CMP_GT2(a, b) ((a) > (b) ? (a) : (b))
      #define CMP_LTE2(a, b) ((a) <= (b) ? (a) : (b))
      #define CMP_GTE2(a, b) ((a) >= (b) ? (a) : (b))
      #define CMP_EQ2(a, b) ((a) == (b))
      #define CMP_NEQ2(a, b) ((a) != (b))
      #define CMP_LT3(a, b, c) (CMP_EQ2(a, b) ? (c) : CMP_LT2(a, b))
      #define CMP_GT3(a, b, c) (CMP_EQ2(a, b) ? (c) : CMP_GT2(a, b))
      #define CMP_LTE3(a, b, c) (CMP_EQ2(a, b) ? (c) : CMP_LT2(a, b))
      #define CMP_GTE3(a, b, c) (CMP_EQ2(a, b) ? (c) : CMP_GT2(a, b))
      #define CMP_EQ3(a, b, c) ((a) == (b) ? (c) : false)
      #define CMP_NEQ3(a, b, c) ((a) != (b) ? true : (c))
      

      然后假设你有:

      struct Point3D {
          double x;
          double y;
          double z;
      };
      

      然后你写:

      struct Point3D {
          double x;
          double y;
          double z;
      
          bool operator<(const Point3D& other) const noexcept
          {
              return CMP_LT3(z, other.z,
                     CMP_LT3(y, other.y,
                     CMP_LT2(x, other.x)));
          }
      };
      

      【讨论】:

        【解决方案6】:

        您可以在 c++11 或更高版本中使用可变参数模板

        template<typename T>
        bool less_than( const T& a, const T& b )
        {
            return a < b;
        }
        
        template<typename T, typename... Args>
        bool less_than( const T& a, const T& b, Args... args )
        (
            if ( a < b )
                  return true;
            else if ( b < a )
                  return false;
            else
                  return less_than(  args...  );
        )
        

        那么你会调用 as

        return less_than(a.x,b.x,
                         a.y,b.y,
                         a.z,b.z);
        

        只要类型有

        【讨论】:

        • 这没有给出有效的严格弱排序。此外,当您可以使用 std::tie(a.x,a.y,a.z) &lt; std::tie(b.x,b.y,b.z) 时,没有真正的理由自己实现此功能。
        • 你能举例说明为什么不做严格的弱排序吗?
        • 好吧,你在我发表评论后编辑了它,所以现在看起来还可以。以前的版本在功能上与a.x &lt; b.x || a.y &lt; b.y || ... 相同,因此它会失败,例如对于 a={2,1,..}, b={1,2,...}。
        • 好的,知道了。一件事是在实践中我不使用这个,而是稍作修改,使用返回 -1,0 或 1 的通用比较,以避免对字符串字段进行两次运算符调用。我认为 tuple / tie 方法更简单/更好,如果它还可以利用字符串比较,那么字符串就不必重复两次。
        猜你喜欢
        • 1970-01-01
        • 2018-05-29
        • 1970-01-01
        • 1970-01-01
        • 2012-10-05
        • 1970-01-01
        相关资源
        最近更新 更多