【问题标题】:Defining operator< for a struct为结构定义 operator<
【发布时间】:2011-04-22 09:36:30
【问题描述】:

我有时使用小的structs 作为映射中的键,因此我必须为它们定义一个operator&lt;。通常,这最终看起来像这样:

struct MyStruct
{
    A a;
    B b;
    C c;

    bool operator<(const MyStruct& rhs) const
    {
        if (a < rhs.a)
        {
           return true;
        }
        else if (a == rhs.a)
        {
            if (b < rhs.b)
            {
                return true;
            }
            else if (b == rhs.b)
            {
                return c < rhs.c;
            }
        }

        return false;
    }
};

这看起来非常冗长且容易出错。有没有更好的方法,或者一些简单的方法来为structclass 自动定义operator&lt;

我知道有些人喜欢只使用memcmp(this, &amp;rhs, sizeof(MyStruct)) &lt; 0 之类的东西,但是如果成员之间存在填充字节,或者如果有char 字符串数组在空终止符之后可能包含垃圾,这可能无法正常工作。

【问题讨论】:

  • 您可以简洁明了,不会更容易出错:return (a &lt; rhs.a || (a == rhs.a &amp;&amp; (b &lt; rhs.b || (b == rhs.b &amp;&amp; c &lt; rhs.c))));

标签: c++ operator-overloading


【解决方案1】:

我认为最简单的方法是坚持使用 或 ==。以下是我遵循的模式,您可以遵循所有结构

typedef struct X
{
    int a;
    std::string b;
    int c;
    std::string d;

    bool operator <( const X& rhs ) const
    {
        if (a < rhs.a) { return true; }
        else if ( rhs.a < a ) { return false; }

        // if neither of the above were true then 
        // we are consdidered equal using strict weak ordering
        // so we move on to compare the next item in the struct

        if (b < rhs.b) { return true; }
        if ( rhs.b < b ) { return false; }

        if (c < rhs.c) { return true; }
        if ( rhs.c < c ) { return false; }

        if (d < rhs.d) { return true; }
        if ( rhs.d < d ) { return false; }

        // if both are completely equal (based on strict weak ordering)
        // then just return false since equality doesn't yield less than
        return false;
    }
};

【讨论】:

  • 你需要 else 做什么?
  • 真的很喜欢
【解决方案2】:

其他人提到了boost::tuple,它为您提供了字典上的比较。如果您想将其保留为具有命名元素的结构,您可以创建临时元组进行比较:

bool operator<(const MyStruct& x, const MyStruct& y)
{
    return boost::make_tuple(x.a,x.b,x.c) < boost::make_tuple(y.a,y.b,y.c);
}

在 C++0x 中,这变为 std::make_tuple()

更新:现在 C++11 就在这里,它变成了std::tie(),在不复制对象的情况下创建一个引用元组。有关详细信息,请参阅 Konrad Rudolph 的新答案。

【讨论】:

  • 我想知道构造这些元组对象对性能有多大影响。
  • @Timo:构造和比较应该被内联,所以如果它比直接比较值慢,我会感到惊讶。但唯一确定的方法是测量它。
  • 如果您需要比较 x.geta(), x.getb(), x.getc() 或其他返回引用的函数,这仍然很好。无法使用领带。
【解决方案3】:

这是一个相当古老的问题,因此这里的所有答案都已过时。 C++11 允许更优雅、更高效的解决方案:

bool operator <(const MyStruct& x, const MyStruct& y) {
    return std::tie(x.a, x.b, x.c) < std::tie(y.a, y.b, y.c);
}

为什么这比使用boost::make_tuple 更好?因为make_tuple 将创建所有数据成员的副本,这可能会很昂贵。相比之下,std::tie 只会创建一个薄薄的引用包装(编译器可能会完全优化掉它)。

事实上,上面的代码现在应该被认为是为具有多个数据成员的结构实现字典比较的惯用解决方案。

【讨论】:

  • 值得一提的是上面的代码不起作用——操作符operator<(const MyStruct& rhs)
  • @Riot 不,代码工作得很好。但是,它确实需要在 MyStruct 之外定义——无论如何这是最佳实践。
  • 使用大结构体和c++1y,可以添加函数auto AsTuple(const MyStruct &amp; s) { return std::tie(s.x, s.y); }。这样可以避免在 operator&lt;.... 中重复结构的字段。不幸的是,我无论如何都没有看到在 c++11 中这样做。
  • @Renaud 在 C++11 中,您可以使用 lambda (auto as_tuple = [](MyStruct const&amp; s) {return std::tie(s.x, s.y);};),因为它可以推断返回类型。
  • @fcatho 我的代码实现了字典比较。并且字典比较是严格的弱排序,它反对称和传递的。
【解决方案4】:

我通常以这种方式实现字典顺序:

bool operator < (const MyObject& obj)
{
    if( first != obj.first ){
        return first < obj.first;
    }
    if( second != obj.second ){
        return second < obj.second;
    }
    if( third != obj.third ){
        return third < obj.third
    }
    ...
}

请注意,浮点值需要额外考虑(G++ 警告),因为这样会更好:

bool operator < (const MyObject& obj)
{
    if( first < obj.first ){
        return true;
    }
    if( first > obj.first ){
        return false;
    }
    if( second < obj.second ){
        return true;
    }
    if( second > obj.second ){
        return false;
    }
    ...
}

【讨论】:

    【解决方案5】:

    如果您不能使用 boost,您可以尝试以下方法:

    #include <iostream>
    
    using namespace std;
    
    template <typename T>
    struct is_gt
    {
      is_gt(const T& l, const T&r) : _s(l > r) {}
    
      template <typename T2>
      inline is_gt<T>& operator()(const T2& l, const T2& r)
      {
        if (!_s)
        {
          _s = l > r;
        }
        return *this;
      }
    
      inline bool operator!() const { return !_s; }
    
      bool _s;
    };
    
    struct foo
    {
      int a;
      int b;
      int c;
    
      friend bool operator<(const foo& l, const foo& r);
    };
    
    bool operator<(const foo& l, const foo& r)
    {
      return !is_gt<int>(l.a, r.a)(l.b, r.b)(l.c, r.c);
    }
    
    int main(void)
    {
      foo s1 = { 1, 4, 8 }, s2 = { 2, 4, 9 };
      cout << "s1 < s2: " << (s1 < s2) << endl;
      return 0;
    }
    

    我想这避免了任何宏,只要结构中的类型支持

    编辑:

    基于 cmets 修改,这个版本现在也应该短路,现在使用两个布尔值来保持状态(不确定有没有办法用一个布尔值来做到这一点)。

    template <typename T>
    struct is_lt
    {
      is_lt(const T& l, const T&r) : _s(l < r), _e(l == r) {}
    
      template <typename T2>
      inline bool operator()(const T2& l, const T2& r)
      {
        if (!_s && _e)
        {
          _s = l < r;
          _e = l == r;
        }
        return _s;
      }
    
      inline operator bool() const { return _s; }
    
      bool _s;
      bool _e;
    };
    

    bool operator<(const foo& l, const foo& r)
    {
      is_lt<int> test(l.a, r.a);
      return test || test(l.b, r.b) || test(l.c, r.c);
    }
    

    只需建立一个此类函子的集合以进行各种比较..

    【讨论】:

    • 如果两个结构相等,这会正常工作吗?在这种情况下,operator
    • 这种方法不允许短路评估 - 有什么方法可以实现吗?
    • @mskfisher - 我猜可以,但是,再想一想......所有这些真正复杂的方法都是毫无意义的,你需要的是||操作员!即,返回 l.a
    • 新的|| 方法不适用于l.a &gt; r.al.b &lt; r.b 的情况——它应该返回false,但它会返回true
    • @mskfisher,哎呀,你是对的 - 漫长的一天......最终编辑应该有一个短路版本,现在操作员不是一个班轮......
    【解决方案6】:

    如果三路比较比二路比较昂贵,并且结构中更重要的部分通常相等,则使用“偏差”参数定义场比较函数可能会有所帮助,这样如果'bias' 为假,当 a>b 时返回真,当偏置为真时,如果 a>=b 则返回真。然后可以通过执行以下操作来确定 a>b 是否:

    返回比较1(a.f1,b.f1,比较2(a.f2,b.f2,比较3(a.f3,b.f3,假)));

    请注意,所有比较都会执行,即使 a.f1b.f1 也是如此,但比较将是双向而不是三向。

    【讨论】:

      【解决方案7】:

      我刚刚学会了boost::tuple 技巧,谢谢@Mike Seymour!

      如果你买不起 Boost,我最喜欢的成语是:

      bool operator<(const MyStruct& rhs) const
      {
          if (a < rhs.a)  return true;
          if (a > rhs.a)  return false;
      
          if (b < rhs.b)  return true;
          if (b > rhs.b)  return false;
      
          return (c < rhs.c);
      }
      

      我喜欢它,因为它将所有内容设置为并行结构,从而更容易发现错误和遗漏。

      但是,当然,无论如何你都在进行单元测试,对吧?

      【讨论】:

      • 请注意,这与@Benoit 的答案基本相同,但没有宏,因此该答案中的 cmets 也适用于此。
      • 谢谢。 @Mark Ransom 关于仅使用 &lt; 的观点已得到适当说明。
      【解决方案8】:

      当您可以在定义字典顺序的元素上生成迭代器时,您可以使用std::lexicographic_compare,来自&lt;algorithm&gt;

      否则我建议基于旧的三值比较函数进行比较,例如如下:

      #include <iostream>
      
      int compared( int a, int b )
      {
          return (a < b? -1 : a == b? 0 : +1);
      }
      
      struct MyStruct
      {
          friend int compared( MyStruct const&, MyStruct const& );
          int a;
          int b;
          int c;
      
          bool operator<( MyStruct const& rhs ) const
          {
              return (compared( *this, rhs ) < 0);
          }
      };
      
      int compared( MyStruct const& lhs, MyStruct const& rhs )
      {
          if( int x = compared( lhs.a, rhs.a ) ) { return x; }
          if( int x = compared( lhs.b, rhs.b ) ) { return x; }
          if( int x = compared( lhs.c, rhs.c ) ) { return x; }
          return 0;
      }
      
      int main()
      {
          MyStruct const  s1 = { 0, 4, 8 };
          MyStruct const  s2 = { 0, 4, 9 };
          std::cout << ( s1 < s2 ? "is less" : "is not less" ) << std::endl;
      }
      

      我将最后一个ifreturn 包含在compare 函数中只是为了通用。我想它可以帮助维护非常严格地坚持一个系统。否则你可以在那里做一个return compared( lhs.c, rhs.c )(也许你更喜欢那个)。

      干杯,

      − 阿尔夫

      【讨论】:

      • @downvoter:请解释您投反对票的原因,以便其他人可以从您的见解中受益,或者让他们忽略投反对票
      【解决方案9】:

      我会这样做:

      #define COMPARE(x) if((x) < (rhs.x)) return true; \
                         if((x) > (rhs.x)) return false;
      COMPARE(a)
      COMPARE(b)
      COMPARE(c)
      return false;
      #undef COMPARE
      

      【讨论】:

      • 只是模板不能替代的那种东西,因为你需要从封闭的函数中返回。一个建议:将(x) &gt; (rhs.x) 替换为(rhs.x) &lt; (x),只依赖operator&lt; 的成员。另外我认为括号是多余的,我看不出这个宏如何在需要它们的输入下正常工作。
      • 我会将最后的 COMPARE(c); return false; 替换为 return c &lt; rhs.c,以避免多余的 > 比较。
      • 你是对的。这是易于阅读和效率之间的折衷问题。
      • 如果您不关心可读性,为什么要使用 if? COMPARE(X,def) (!(rhs.x 可能优化和可读的代码更容易优化
      【解决方案10】:

      我知道的最好方法是使用boost tuple。它提供了一个内置的比较和构造函数。

      #include <boost/tuple/tuple.hpp>
      #include <boost/tuple/tuple_comparison.hpp>
      
      typedef boost::tuple<int,int,int> MyStruct;
      
      MyStruct x0(1,2,3), x1(1,2,2);
      if( x0 < x1 )
         ...
      

      我也喜欢 Mike Seymors suggestion to use temporary tuples through boost's make_tuple

      【讨论】:

      • 是的……但是当它涉及复杂结构时,它的表现是否良好?
      • 为什么不能很好地发挥作用?这项工作发生在编译时。
      【解决方案11】:
      bool operator <(const A& l, const A& r)
      {
      
          int[] offsets = { offsetof(A, a), offsetof(A, b), offsetof(A, c) };
          for(int i = 0; i < sizeof(offsets)/sizeof(int); i++)
          {
              int ta = *(int*)(((const char*)&l)+offsets[i]);
              int tb = *(int*)(((const char*)&r)+offsets[i]);
      
              if (ta < tb)
                   return true;
              else if (ta > tb)
                   break;
      
          }
          return false;
      }
      

      【讨论】:

      • 如果超过 3 个成员怎么办
      • simple -> 只需将它们的偏移量添加到offsets 数组中
      • 如果你打算用它来实现op
      【解决方案12】:
      #include <iostream>
      
      #include <boost/fusion/include/adapt_struct.hpp>
      #include <boost/fusion/include/less.hpp>
      
      struct MyStruct {
         int a, b, c;
      };
      
      BOOST_FUSION_ADAPT_STRUCT( MyStruct,
                                 ( int, a )
                                 ( int, b )
                                 ( int, c )
                                )
      
      bool operator<( const MyStruct &s1, const MyStruct &s2 )
      {
         return boost::fusion::less( s1, s2 );
      }
      
      int main()
      {
         MyStruct s1 = { 0, 4, 8 }, s2 = { 0, 4, 9 };
         std::cout << ( s1 < s2 ? "is less" : "is not less" ) << std::endl;
      }
      

      【讨论】:

        【解决方案13】:

        我写了一个 perl 脚本来帮助我。例如给出:

        class A
        {
        int a;
        int b;
        int c;
        

        它会发出:

        bool operator<(const A& left, const A& right)
        {
            bool result(false);
        
            if(left.a != right.a)
            {
                result = left.a < right.a;
            }
            else if(left.b != right.b)
            {
                result = left.b < right.b;
            }
            else
            {
                result = left.c < right.c;
            }
        
            return result;
        }
        

        代码(有点长):

        #!/usr/bin/perl
        
        use strict;
        
        main:
        
        my $line = <>;
        chomp $line;
        $line =~ s/^ *//;
        
        my ($temp, $line, $temp) = split / /, $line;
        
        print "bool operator<(const $line& left, const $line& right)\n{\n";
        print "    bool result(false);\n\n";
        
        my $ifText = "if";
        
        $line = <>;
        
        while($line)
        {
            if($line =~ /{/)
            {
                $line = <>;
                next;
            }
            if($line =~ /}/)
            {
                last;
            }
        
            chomp $line;
            $line =~ s/^ *//;
        
            my ($type, $name) = split / /, $line;
            $name =~ s/; *$//;
        
            $line = <>;
            if($line && !($line =~ /}/))
            {
                print "    $ifText(left.$name != right.$name)\n";
                print "    {\n";
                print "        result = left.$name < right.$name;\n";
                print "    }\n";
        
                $ifText = "else if";
            }
            else
            {
                print "    else\n";
                print "    {\n";
                print "        result = left.$name < right.$name;\n";
                print "    }\n";
        
                last;
            }
        }
        
        print "\n    return result;\n}\n";
        

        【讨论】:

        • 对象不相等通常更常见,因此我会修改您的比较以使用 op
        • @Roger Pate 表示同意,但我无法完全想象代码会是什么样子,您能否简要说明一下?
        • if (left.a != left.b) { return left.a &lt; left.b; } 变为 if (left.a &lt; left.b) return true; else if (left.a != left.b) return false; (或者你可以使用结果变量,同样的事情)
        【解决方案14】:

        在这种情况下,您可以使用boost::tuple&lt;int, int, int&gt; - 它的operator< 可以按照您想要的方式工作。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-11-18
          • 1970-01-01
          • 1970-01-01
          • 2017-07-08
          • 2015-09-13
          • 1970-01-01
          相关资源
          最近更新 更多