定义运算符<对于结构

时间:2010-10-07 14:09:33

标签: c++ operator-overloading

我有时会在地图中使用小structs作为键,因此我必须为它们定义operator<。通常,这最终看起来像这样:

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

这看起来非常冗长且容易出错。有没有更好的方法或一些简单的方法来自动定义operator<struct的{​​{1}}?

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

14 个答案:

答案 0 :(得分:90)

这是一个相当古老的问题,因此这里的所有答案都已过时。 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只会创建一个瘦的引用包装(编译器可能会完全优化掉)。

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

答案 1 :(得分:18)

其他人提到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的新答案。

答案 2 :(得分: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

答案 3 :(得分:6)

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

答案 4 :(得分:3)

我知道的最好方法是使用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

答案 5 :(得分:3)

我认为最简单的方法是坚持使用&lt;所有比较的运算符,不要使用&gt;或==。以下是我遵循的模式,您可以关注所有结构

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

答案 6 :(得分:3)

我通常以这种方式实现词典排序:

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

答案 7 :(得分:2)

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

答案 8 :(得分:2)

如果你不能使用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;
}

我想这可以避免任何宏,并且只要结构中的类型支持&lt;,它就可以工作。当然,这种方法有开销,如果其中一个值更大,则为每个参数构建is_gt然后构造超级分支......

修改

根据评论进行修改,此版本现在也应该短路,现在使用两个bool来保持状态(不确定是否有办法用单个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;
};

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

只需构建一系列此类仿函数进行各种比较..

答案 9 :(得分:1)

我刚刚学会了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);
}

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

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

答案 10 :(得分:0)

我写了一个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";

答案 11 :(得分:0)

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

答案 12 :(得分:0)

如果可以在定义词典顺序的元素上生成迭代器,则可以使用std::lexicographic_compare中的<algorithm>

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

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

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

干杯&amp;第h。,

- Alf

答案 13 :(得分:0)

如果三向比较比双向更昂贵,并且如果结构的更重要部分通常相等,则使用'bias'参数定义字段比较函数可能会有所帮助,如果'bias'是错误的,当a> b时它们将返回true,而当bias为true时,如果a> = b,它们将返回true。然后,人们可以通过执行以下操作找出&gt; b:

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

请注意,即使a.f1&lt;&gt; b.f1,也会执行所有比较,但比较将是双向的,而不是三向的。