三路运算符是由Herb Sutter提出,2019年10月8日确定的C++20草案中正式将三路运算符纳入进来。在类中使用三路运算符后,编译器可以默认生成6个基础运算符,这一新特性的使用从一定程度上来说减少了开发的工作量,因此也受到大家的喜爱,被大家称为:宇宙飞船运算符。
1 三路运算符的形式
三路运算符不同于6中基础的运算符,C++标准委员会使用“<=>”作为三路运算符的操作符。表达式形式如下:
左操作数 <=> 右操作数
三路运算符返回的是一个对象,如下所示:
三路运算符的返回结果和我们实际中使用的strcmp、strncmp一样,但是又有本质的区别,三路运算符返回的是std::strong_ordering、std::weak_ordering以及std::partial_ordering对象,而strcmp和strncmp返回的是整型数据。
2 默认比较
在编程时,类类型可以生成默认比较,在类中定义后,编译器会默认生成6种比较运算符的代码,生成默认比较的形式如下所示:
//类成员函数定义
返回类型 类名::operator运算符( const 类名 & ) const &(可选) = default;
//非成员函数
friend 返回类型 operator运算符( const 类名 &, const 类名 & ) = default;
//非成员函数,按值传递
friend 返回类型 operator运算符( 类名, 类名 ) = default;
三路运算符使用时,有自己限制,既:返回结果必须是auto或者std::strong_ordering、std::weak_ordering以及std::partial_ordering中的任意一个。
默认比较代码演示如下:
class Point {
int x;
int y;
public:
Point(int _x,int _y):x(_x),y(_y){};
//预制生成默认比较函数,编译器会生成<,==,<,<=,>=,!=运算代码
auto operator<=>(const Point&) const = default;
};
int main() {
Point pt1{1, 1}, pt2{1, 2};
std::set<Point> s; // ok
s.insert(pt1); // ok
std::cout << std::boolalpha
<< (pt1 == pt2) << ' ' // false;operator== 隐式地采用默认版本
<< (pt1 != pt2) << ' ' // true
<< (pt1 < pt2) << ' ' // true
<< (pt1 <= pt2) << ' ' // true
<< (pt1 > pt2) << ' ' // false
<< (pt1 >= pt2) << ' ';// false
}
代码运行结果为:
false true true true false false
3 用户自定义比较
实际编码时,有些场景需要我们自己定义三路运算符比较,编译器可以根据我们的定义规则生成相应的比较运算符代码。按照返回结果可以分为三种,分别是:
3.1 强序
返回对象为强序时,需要对每个对象进行比较操作,比价顺序可以根据需求自己定义。如下面的例子,使用强序作为返回对象。
struct TotallyOrdered {
std::string tax_id;
std::string first_name;
std::string last_name;
public:
// 自定义 operator<=> ,因为我们希望(在比较姓前)先比较名:
std::strong_ordering operator<=>(const TotallyOrdered& that) const {
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;
}
// ... 非比较函数 ...
};
int main() {
TotallyOrdered to1{"b","c","d"}, to2{"b","d","c"};
auto res = to1<=>to2;
if(res >0)
std::cout<<"to1>to2"<<std::endl;
else if(res < 0)
std::cout<<"to1<to2"<<std::endl;
else
std::cout<<"to1==to2"<<std::endl;
}
运行结果为:
to1>to2
3.2 弱序
返回结果为std::weak_ordering,下面的代码就是生成对字符串比较时忽略大小写。代码如下:
class CaseInsensitiveString {
std::string strS;
int is_same(const CaseInsensitiveString& rhs) const {
return strcasecmp(strS.c_str(),rhs.strS.c_str());
}
public:
CaseInsensitiveString(std::string _str):strS(_str){};
std::weak_ordering operator<=>(const CaseInsensitiveString& b) const {
if(is_same(b)>0) return std::weak_ordering::greater;
else if(is_same(b)<0) return std::weak_ordering::less;
else return std::weak_ordering::equivalent;
}
};
int main() {
CaseInsensitiveString str1("CPP"), str2("cpp");
auto res = str1<=>str2;
if(res >0)
std::cout<<"to1>to2"<<std::endl;
else if(res < 0)
std::cout<<"to1<to2"<<std::endl;
else
std::cout<<"to1==to2"<<std::endl;
}
运行结果如下:
to1==to2
3.3 偏序
偏序是一种允许无法比较(无序)的值的比较的排序,比如包括 NaN 值的浮点排序。
class CaseInsensitiveString {
std::string strS;
int is_same(const CaseInsensitiveString& rhs) const {
return strcasecmp(strS.c_str(),rhs.strS.c_str());
}
public:
CaseInsensitiveString(std::string _str):strS(_str){};
std::partial_ordering operator<=>(const CaseInsensitiveString& b) const {
if(is_same(b)>0) return std::partial_ordering::greater;
else if(is_same(b)<0) return std::partial_ordering::less;
else return std::partial_ordering::equivalent;
}
};
int main() {
CaseInsensitiveString str1("CPP"), str2("cpp");
if(std::is_eq(str1<=>str2)) std::cout<<"str1 is str2";
if(std::is_neq(str1<=>str2)) std::cout<<" str1 is not str2";
if(str1>str2) std::cout<<" str1>str2";
if(str1<str2) std::cout<<" str1<str2";
}
运行结果为:
str1 is str2
总结:
三路运算中,如果需要对类成员中每一个成员都进行比较,使用默认比较既可,如果不需要,则可以根据需要进行自定义生成。对于偏序小编的理解可能存在一定的差异,这里希望大家留言评论。