例如:在C ++头文件中,如果我定义了struct Record
并且我想将它用于可能的排序,那么我想重载less operator
。以下是我在各种代码中注意到的三种方式。我粗略地注意到:如果我要将Record
放入std::set
,map
,priority_queue
,...容器,则版本2可以运行(可能还有版本3) );如果我要将Record
保存到vector<Record> v
然后再拨打make_heap(v.begin(), v.end())
等,那么只有版本1有效。
struct Record
{
char c;
int num;
//version 1
bool operator <(const Record& rhs)
{
return this->num>rhs.num;
}
//version 2
friend bool operator <(const Record& lhs, const Record& rhs) //friend claim has to be here
{
return lhs->num>rhs->num;
}
};
在相同的头文件中,例如:
//version 3
inline bool operator <(const Record& lhs, const Record& rhs)
{
return lhs->num>rhs->num;
}
基本上,我想在这里提出问题,看看是否有人能够总结一下这三种方法之间的差异以及每个版本的正确位置是什么?
答案 0 :(得分:5)
它们本质上是相同的,除了第一个是非const之外,允许你自己修改。
我更喜欢第二个原因:
friend
。lhs
不一定是Record
答案 1 :(得分:5)
定义less运算符的最佳方法是:
struct Record{
(...)
const bool operator < ( const Record &r ) const{
return ( num < r.num );
}
};
答案 2 :(得分:0)
bool operator <(const Record& rhs);
是
bool operator <(Record& lhs, const Record& rhs); // lhs is non-const
现在 STL 容器将它们存储的项目视为 const
(至少就比较运算符而言)。所以他们称您的运营商的 const-const
变体。如果他们没有找到它(如果您只提供了变体 1) - 这是一个编译错误。
const-const
会员和 const-const
非会员: struct Record
{
bool operator <(const Record& rhs) const;
};
bool operator <(Record& lhs, const Record& rhs);
这是另一个编译器错误,因为这样的定义会导致歧义:
<块引用>如果在找到匹配项的最高级别找到两个匹配项, 该呼叫被拒绝为模棱两可。 /Stroustrup,C++,第 12.3.1 节/
friend
修饰符,因为默认情况下 struct
的所有字段都是公开的。PS make_heap
不期望比较项为 const
,因为它是一个更底层的野兽,使用它你就像在共同创作一个新的基于堆的库,所以您有责任跟踪项目的稳定性。
PPS set
将项目作为 const
处理并不能保护您在将项目插入容器后修改其密钥 - 如果您尝试,它将导致运行时错误(段错误)
答案 3 :(得分:0)
欢迎来到 c++20,我们有更多选择。
//version 1
bool operator <(const Record& rhs)
{
return this->num>rhs.num;
}
这是错误的,应该是:
//version 1
bool operator <(const Record& rhs)const
{
return this->num>rhs.num;
}
因为您希望左侧也具有 const 限定。
//version 2
friend bool operator <(const Record& lhs, const Record& rhs) //friend claim has to be here
{
return lhs->num>rhs->num;
}
这个是对称的。因此,假设您有一个带有 struct Bar
的 operator Record
。
然后
Record rhs;
Bar lhs;
assert( lhs < bar );
以上适用于对称情况,但不适用于成员函数版本。
类版本中的 friend
是一个操作符,只能通过 Koenig 查找(Argument Dependent Lookup)找到。当您希望将对称运算符(或类型在右侧的运算符,如 ostream&<<*this
)绑定到特定模板类实例时,这非常有用。
如果在类之外,则必须是模板函数,模板函数的重载方式与非模板函数不同;非模板函数允许转换。
template<class T>
struct point {
T x ,y;
point operator-(point const& rhs)const{
return {x-rhs.x,y-rhs.y};
}
friend point operator+(point const& lhs, point const& rhs) {
return {lhs.x+rhs.x, lhs.y+rhs.y};
}
};
template<class T>
point<T> operator*( point<T> const& lhs, point<T> const& rhs ) {
return {lhs.x*rhs.x, lhs.y*rhs.y};
}
这里的 -
是非对称的,所以如果我们有一个类型转换为左边的 point<int>
,将找不到 -
。
+
是对称的并且是“Koenig 运算符”,因此它是一个非模板运算符。
*
是对称的,但它是一个模板运算符。如果您有转换为点的内容,它不会找到 *
重载,因为推导会失败。
//version 3
inline bool operator <(const Record& lhs, const Record& rhs)
{
return lhs->num>rhs->num;
}
这与上面的 template
类似,但这里不会出现该问题。这里的区别是,你可以在类外获取这个函数的地址,而你写的“koenig operator<
”只能通过ADL找到。哦,这不是朋友。
c++17 添加
auto operator<=>(const Record&)=default;
我们使用宇宙飞船运算符 <=>
来自动定义排序。
这将使用 c
和 num
的顺序来生成所需的结果。
很像规则 5,您应该设法使 =default
在这里正常工作。拥有 <
忽略的状态是一种难闻的气味,纠缠状态的不同部分也是如此。
答案 4 :(得分:-3)
除非因为第一个参数是错误的类型而不能在课堂上,否则在课堂上受欢迎。