[英]Defining operator< for a struct
有時我會在地圖中使用小型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;
}
};
這似乎很冗長且容易出錯。 是否有更好的方法或某種簡單的方法來自動化對struct
或class
的operator<
定義?
我知道有些人只喜歡使用諸如memcmp(this, &rhs, sizeof(MyStruct)) < 0
,但是如果成員之間存在填充字節,或者如果有可能包含垃圾的char
字符串數組,這可能無法正常工作在空終止符之后。
這是一個很老的問題,因此,這里的所有答案都已過時。 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
只會創建一個薄的引用包裝(編譯器可能會完全優化它們)。
實際上,現在應該將上述代碼視為對具有多個數據成員的結構實施詞典比較的慣用解決方案。
其他人提到了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的新答案。
我會這樣做:
#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 <可以按照您想要的方式工作。
我認為最簡單的方法是在所有比較中堅持使用<運算符,而不使用>或==。 以下是我遵循的模式,您可以遵循所有結構
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;
}
};
我所知道的最好方法是使用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的建議,通過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;
}
...
}
#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;
}
如果您不能使用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;
}
我猜這可以避免任何宏,只要結構中的類型支持<,它就應該起作用。 當然,這種方法會有開銷,構造is_gt,然后如果值之一更大則為每個參數構造多余的分支...
編輯:
根據注釋進行了修改,該版本現在也應該短路,現在使用兩個布爾值來保持狀態(不確定是否可以通過單個布爾值來做到這一點)。
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);
}
只需建立此類函子的集合以進行各種比較即可。
我剛剛學會了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);
}
我喜歡它,因為它將所有內容都設置為並行結構,這樣可以更容易發現錯誤和遺漏。
但是,當然,您正在對此進行單元測試,對嗎?
我寫了一個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
。
否則,我建議根據舊的三值比較函數進行比較,例如:
#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 )
的return compared( lhs.c, rhs.c )
(也許您更喜歡這樣做)。
干杯,……
-Alf
如果三向比較比兩向比較昂貴,並且結構的重要部分通常相等,則使用“ bias”參數定義字段比較函數可能會有所幫助,例如,如果“ bias”如果為false,則當a> b時,它們將返回true;當bias為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,也將執行所有比較,但是比較將是兩路而不是三路。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.