C++20 的三向比較(Three-way comparison)

x33g5p2x  于2022-02-12 转载在 C/C++  
字(2.4k)|赞(0)|评价(0)|浏览(390)

三向比较(three-way comparison、参考)是C++20新增的一项新的运算子,他的形式是「<=>」;据说是由于外型的关系,所以也被称为「Spaceship Operator」。

而它的特色呢,则是可以针对两个变数进行比较,并透过一个回传值让使用者可以判断到底是大于、小于、还是等于;基本上是:

a < b 的话:(a <=> b) < 0
a > b 的话:(a <=> b) > 0
a 和b 相等或等价的话:(a <=> b) == 0

而实际上,根据变数的型别的不同,他可能会回传不同型别的结果来做区隔。

在这个新的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来做判断,不过里也有提供命名的函式、来方便使用;包括了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<=>的内容来达成的~

相关文章