三向比較(three-way comparison、參考)是 C++20 新增的一項新的運算子,他的形式是「<=>」;據說是由於外型的關係,所以也被稱為「Spaceship Operator」。
而它的特色呢,則是可以針對兩個變數進行比較,並透過一個回傳值讓使用者可以判斷到底是大於、小於、還是等於;基本上是:
- a < b 的話:(a <=> b) < 0
- a > b 的話:(a <=> b) > 0
- a 和 b 相等或等價的話:(a <=> b) == 0
而實際上,根據變數的型別的不同,他可能會回傳不同型別的結果來做區隔。
在 <compare> 這個新的 header 檔(文件)裡面,就還有定義出三種不同的比較結果:
- std::strong_ordering
- 比較結果有 less、greater、equal、equivalent,後兩者基本上相同
- 整數型別的比較結果就是這種
-
- std::weak_ordering
- 比較結果有 less、greater、equivalent
- 和 std::strong_ordering
的差別是只有 equivalent
而沒有 equal
- std::partial_ordering
- 比較結果有 less、greater、equivalent、unordered
- 多了無法比較的狀況,也就是 unordered
- 浮點數的比較結果會是這種,如果其中有一個是 NaN 就會變成 unordered
而雖然這三種型別的結果都可以用 == 0、> 0、< 0 來做判斷,不過 <compare> 裡也有提供命名的函式、來方便使用;包括了 is_eq()、is_neq()、is_lt()、is_lteq()、is_gt()、is_gteq(),可以試需要使用。
下面就是一個簡單的使用例子:
auto res = (1.0f <=> 1.0f); if (std::is_eq(res)) std::cout << "equal\n"; else std::cout << "not equal\n";
另外,按照強度的關係,也可以把較強的比較結果轉換成較弱的結果,其關係就是:
strong_ordering -> weak_ordering -> partial_ordering
反過來則無法直接轉換;下面是個簡單的例子:
std::strong_ordering so = std::strong_ordering::equal; std::weak_ordering wo = so; std::partial_ordering po = so; std::weak_ordering wo1 = po; //ERROR
而在 Heresy 來看,定義三向比較的函式比較大的好處,在於如果針對自己定義的類別定義了 operator<=> 後,編譯器就可以自動產生其他的比較函式、讓這個類別可以做任意的比較了!
它會自動產生的比較函式包括了:==、!=、<、<=、>、>= 這六個。
在最簡單的狀況下,只要加入一行、告訴編譯器要使用預設的三向比較函式就可以了;下面就是一個例子:
struct SData { int iVal = 0; auto operator<=>(const SData&) const = default; };
如此一來,SData 這個型別的資料,就可以在同型別的資料之間做各種比較,而不需要自己去各自定義不同的比較函式了。
預設的 operator<=> 會進行字典式的比較(lexicographical comparison),順序是先比較 base class(由左而右、深度優先)、然後再按照宣告順序來比較 non-static 的成員資料;而針對陣列,他應該也是會去依序比較內容。
這些比較會依序進行、在遇到不相等的時候就會停下來、回傳比較的結果;所以在一般狀況下,預設的比較方式應該算是可以直接拿來用的。
而如果預設的比較模式不符合需求的話,也可以自己定義比較的方法。
在《Default comparisons》,也提供了一些自訂比較方法的例子。
像是如果只是想要修改比較順序的話,可以寫成:
struct Base { std::string zip; auto operator<=>(const Base&) const = default; }; struct TotallyOrdered : Base { std::string tax_id; std::string first_name; std::string last_name; public: // custom operator<=> because we want to compare last names first: std::strong_ordering operator<=>(const TotallyOrdered& that) const { if (auto cmp = (Base&)(*this) <=> (Base&)that; cmp != 0) return cmp; if (auto cmp = last_name <=> that.last_name; cmp != 0) return cmp; if (auto cmp = first_name <=> that.first_name; cmp != 0) return cmp; return tax_id <=> that.tax_id; } // ... non-comparison functions ... };
他會先去用預設的方法比較基礎型別 Base,然後在按照 last_name、first_name、tax_id 的順序來比較資料。
而如果有需要撰寫進一步的比較方法,也是可以透過自己定義 operator<=> 的內容來達成的~
參考: