定义一个结构体的运算符<
我有时使用小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
或class
?
我知道有些人喜欢使用memcmp(this, &rhs, sizeof(MyStruct)) < 0
,但是如果成员之间有填充字节,或者存在可能包含垃圾的char
string数组,在空终止后。
这是一个相当古老的问题,因此这里的所有答案都已经过时了。 C ++ 11允许更优雅和高效的解决scheme:
bool operator <(const MyStruct& x, const MyStruct& y) { return std::tie(xa, xb, xc) < std::tie(ya, yb, yc); }
为什么这比使用boost::make_tuple
更好? 因为make_tuple
将创build所有数据成员的副本,这可能是昂贵的。 相反, std::tie
只会创build一个很简单的引用包装(编译器可能会完全优化)。
实际上,上面的代码现在应该被认为是实现对具有多个数据成员的结构的词典比较的惯用解决scheme。
其他人提到boost::tuple
,它给你一个词典比较。 如果你想保留它作为一个具有命名元素的结构,你可以创build临时元组进行比较:
bool operator<(const MyStruct& x, const MyStruct& y) { return boost::make_tuple(xa,xb,xc) < boost::make_tuple(ya,yb,yc); }
在C ++ 0x,这成为std::make_tuple()
。
更新:现在C ++ 11在这里,它变成std::tie()
,使一个引用的元组而不复制对象。 有关详情,请参阅康拉德·鲁道夫的新答案。
我会这样做:
#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>
– 它的运算符<按你想要的方式工作。
我知道的最好的方法是使用boost元组 。 它提供了一个内置的比较和构造函数。
#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 build议通过boost的make_tuple使用临时元组
我通常以这种方式实现字典顺序:
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; } ... }
我认为最简单的方法是坚持使用<运算符进行所有比较,不要使用>或==。 下面是我遵循的模式,你可以按照你所有的结构
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; } };
#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; }
如果你不能使用提升,你可以尝试像这样:
#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>(la, ra)(lb, rb)(lc, rc); } int main(void) { foo s1 = { 1, 4, 8 }, s2 = { 2, 4, 9 }; cout << "s1 < s2: " << (s1 < s2) << endl; return 0; }
我想这避免了任何macros,只要结构中的types支持<,它应该工作。 当然,这种方法有开销,如果其中一个值更大,则为每个参数构造is_gt,然后构造超级分支…
编辑:
现在使用两个bools保持状态(不确定是否有一个方法可以用一个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(la, ra); return test || test(lb, rb) || test(lc, rc); }
只是build立一个这样的function集合进行各种比较..
我刚刚学会了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); }
我喜欢它,因为它把所有的东西放在并行的结构中,使错误和遗漏更容易被发现。
但是,当然,你是unit testing呢,对吧?
我写了一个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";
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; }
当你可以在定义字典顺序的元素上生成迭代器时,你可以使用<algorithm>
std::lexicographic_compare
。
否则,我build议根据旧的三值比较函数进行比较,如下所示:
#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
和return
compare
函数只是为了一般性。 我想它可以帮助维护非常严格地坚持一个单一的系统。 否则,你可以做一个return compared( lhs.c, rhs.c )
那里(也许你更喜欢那个)。
干杯&hth。,
– Alf
如果三向比较比双向更昂贵,并且如果结构的更重要的部分通常是相等的,则用“偏差”参数定义场比较函数可能是有帮助的,例如,如果“偏差”是假的,当a> b时,它们将返回true,当偏差为真时,如果a> = b,它们将返回true。 然后,可以通过执行以下操作来查找a> b:
return compare1(a.f1,b.f1,compare2(a.f2,b.f2,compare3(a.f3,b.f3,false)));
请注意,所有比较都将执行,即使a.f1 <> b.f1,但比较将是双向而不是三路。