简体   繁体   English

为结构定义operator <

[英]Defining operator< for a struct

I sometimes use small structs as keys in maps, and so I have to define an operator< for them. 有时我会在地图中使用小型structs作为键,因此必须为其定义一个operator< Usually, this ends up looking something like this: 通常,这最终看起来像这样:

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;
    }
};

This seems awfully verbose and error-prone. 这似乎很冗长且容易出错。 Is there a better way, or some easy way to automate definition of operator< for a struct or class ? 是否有更好的方法或某种简单的方法来自动化对structclassoperator<定义?

I know some people like to just use something like memcmp(this, &rhs, sizeof(MyStruct)) < 0 , but this may not work correctly if there are padding bytes between the members, or if there are char string arrays that may contain garbage after the null terminators. 我知道有些人只喜欢使用诸如memcmp(this, &rhs, sizeof(MyStruct)) < 0 ,但是如果成员之间存在填充字节,或者如果有可能包含垃圾的char字符串数组,这可能无法正常工作在空终止符之后。

This is quite an old question and as a consequence all answers here are obsolete. 这是一个很老的问题,因此,这里的所有答案都已过时。 C++11 allows a more elegant and efficient solution: 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);
}

Why is this better than using boost::make_tuple ? 为什么这比使用boost::make_tuple更好? Because make_tuple will create copies of all the data members, which can be costly. 因为make_tuple将创建所有数据成员的副本,这可能会很昂贵。 std::tie , by contrast, will just create a thin wrapper of references (which the compiler will probably optimise away entirely). 相比之下, std::tie只会创建一个薄的引用包装(编译器可能会完全优化它们)。

In fact, the above code should now be considered the idiomatic solution to implementing a lexicographical compare for structures with several data members. 实际上,现在应该将上述代码视为对具有多个数据成员的结构实施词典比较的惯用解决方案。

Others have mentioned boost::tuple , which gives you a lexicographical comparison. 其他人提到了boost::tuple ,它为您提供了字典上的比较。 If you want to keep it as a structure with named elements, you can create temporary tuples for comparison: 如果要将其保留为具有命名元素的结构,则可以创建临时元组进行比较:

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);
}

In C++0x, this becomes std::make_tuple() . 在C ++ 0x中,它变为std::make_tuple()

UPDATE: And now C++11 is here, it becomes std::tie() , to make a tuple of references without copying the objects. 更新:现在C ++ 11在这里,它变成std::tie() ,以在不复制对象的情况下创建引用元组。 See Konrad Rudolph's new answer for details. 有关详细信息,请参见Konrad Rudolph的新答案。

I would do this: 我会这样做:

#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

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

I think the easiest way is to stick with the < operator for all comparisons and don't use > or ==. 我认为最简单的方法是在所有比较中坚持使用<运算符,而不使用>或==。 Below is the pattern I follow, and you can follow for all your structs 以下是我遵循的模式,您可以遵循所有结构

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;
    }
};

The best way I know is to use a boost tuple . 我所知道的最好方法是使用boost元组 It offers among others a builtin comparison and constructors. 它提供了内置的比较和构造函数。

#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 )
   ...

I also like Mike Seymors suggestion to use temporary tuples through boost's make_tuple 我也喜欢Mike Seymors的建议,通过boost的make_tuple使用临时元组。

I usually implement lexicographical ordering this way: 我通常以这种方式实现字典顺序:

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
    }
    ...
}

Mind you it needs extra consideration for floating point values (G++ warnings), for those something like this would be better: 请注意,您需要特别考虑浮点值(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;
    }
    ...
}
#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;
}

if you can't use boost, you could try something like: 如果您不能使用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;
}

I guess this avoids any macros, and as long as the types in the structure support <, it should work. 我猜这可以避免任何宏,只要结构中的类型支持<,它就应该起作用。 Of course there is overhead for this approach, constructing is_gt and then superflous branches for each parameter if one of the values is greater... 当然,这种方法会有开销,构造is_gt,然后如果值之一更大则为每个参数构造多余的分支...

Edit: 编辑:

Modified based on comments, this version should now short-circuit as well, now uses two bools to keep state (not sure there's a way to do this with a single bool). 根据注释进行了修改,该版本现在也应该短路,现在使用两个布尔值来保持状态(不确定是否可以通过单个布尔值来做到这一点)。

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;
};

and

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);
}

just build up a collection of such functors for various comparisons.. 只需建立此类函子的集合以进行各种比较即可。

I just learned the boost::tuple trick, thanks, @Mike Seymour! 我刚刚学会了boost::tuple技巧,谢谢@Mike Seymour!

If you can't afford Boost, my favorite idiom is: 如果您买不起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);
}

which I like because it sets everything in parallel structure that makes errors and omissions easier to spot. 我喜欢它,因为它将所有内容都设置为并行结构,这样可以更容易发现错误和遗漏。

But, of course, you are unit testing this anyway, right? 但是,当然,您正在对此进行单元测试,对吗?

I wrote a perl script to help me. 我写了一个perl脚本来帮助我。 For example given: 例如给出:

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

It would emit: 它会发出:

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;
}

Code (it's a bit long): 代码(有点长):

#!/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";
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;
}

When you can produce iterators over the elements defining the lexicographic order you can use std::lexicographic_compare , from <algorithm> . 当您可以在定义词典顺序的元素上生成迭代器时,可以使用<algorithm> std::lexicographic_compare

Otherwise I suggest basing comparisons on old three-value compare functions, eg as follows: 否则,我建议根据旧的三值比较函数进行比较,例如:

#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;
}

I included the last if and return in the compare function just for generality. 我只是为了笼统地将最后一个ifreturn包含在compare函数中。 I imagine it can help maintenance to very rigidly adhere to a single system. 我想它可以帮助维护人员非常严格地遵守单个系统。 Otherwise you could just do a return compared( lhs.c, rhs.c ) there (and perhaps you prefer that). 否则,您可以在那里进行return compared( lhs.c, rhs.c )return compared( lhs.c, rhs.c ) (也许您更喜欢这样做)。

Cheers & hth., 干杯,……

− Alf -Alf

If three-way comparisons are more expensive than two-way, and if the more-significant portions of the structures will often be equal, it may be helpful to define field comparison functions with a 'bias' parameter, such that if 'bias' is false, they will return true when a>b, and when bias is true, they will return true if a>=b. 如果三向比较比两向比较昂贵,并且结构的重要部分通常相等,则使用“ bias”参数定义字段比较函数可能会有所帮助,例如,如果“ bias”如果为false,则当a> b时,它们将返回true;当bias为true时,如果a> = b,则它们将返回true。 Then one can find out if a>b by doing something like: 然后可以通过执行以下操作找出a> b是否为:

return compare1(a.f1,b.f1, compare2(a.f2,b.f2, compare3(a.f3,b.f3,false)));

Note that all comparisons will be performed, even if a.f1<>b.f1, but comparisons will be two-way instead of three-way. 请注意,即使a.f1 <> b.f1,也将执行所有比较,但是比较将是两路而不是三路。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM