注意:我认为这在技术上与此问题重复,但是:
- C++20 中的更改
==
非常激进,我不确定恢复 9 年的问题是否正确。 - 我特别询问了运算符
==
以及<=>
正在被编译器重写的运算符,而不是例如 operator<
。
ps 目前我有自己的看法(基于 foonathan 的一些谈话),但这只是当前的偏好,我不希望对潜在的答案产生偏见。
注意:我认为这在技术上与此问题重复,但是:
==
非常激进,我不确定恢复 9 年的问题是否正确。==
以及<=>
正在被编译器重写的运算符,而不是例如 operator <
。ps 目前我有自己的看法(基于 foonathan 的一些谈话),但这只是当前的偏好,我不希望对潜在的答案产生偏见。
我认为在 C++20 中,比较应该是成员函数,除非你有非常令人信服的理由。
Lemme 首先从 C++17 演算开始:我们经常将比较写为非成员。这样做的原因是它是允许双向比较的唯一方法。如果我有一个X
我想与之相媲美的类型int
,我就不能1 == X{}
使用成员函数——它必须是一个自由函数:
struct X { };
bool operator==(X, int);
bool operator==(int lhs, X rhs) { return rhs == lhs; }
在这件事上没有太多选择。现在,把这些写成纯粹的自由函数是次优的,因为我们污染了命名空间并增加了查找中的候选者的数量——所以最好让它们成为隐藏的朋友:
struct X {
friend bool operator==(X, int);
friend bool operator==(int lhs, X rhs) { return rhs == lhs; }
};
在 C++20 中,我们没有这个问题,因为比较本身是对称的。你可以写:
struct X {
bool operator==(int) const;
};
并且仅该声明就已经允许X{} == 1
and 1 == X{}
,同时还没有为名称查找提供额外的候选者(如果一侧或另一侧是 ,它将已经是候选者X
)。
此外,在 C++20 中,如果在类的声明中声明它们,则可以默认比较。这些可以是成员函数或隐藏的朋友,但不是外部自由函数。
提供非成员比较的一个有趣案例是我遇到的std::string
. 该类型的比较当前是非成员函数模板:
template<class charT, class traits, class Allocator>
constexpr bool
operator==(const basic_string<charT, traits, Allocator>& lhs,
const basic_string<charT, traits, Allocator>& rhs) noexcept;
这与使它成为成员(非模板)函数或隐藏的朋友(非模板)函数具有重要的不同语义,因为它不允许通过作为模板进行隐式转换。正如我所指出的,将此比较运算符转换为非模板会产生突然允许双方进行隐式转换的效果,这可能会破坏以前没有意识到这种可能性的代码。
但无论如何,如果您有一个类模板并希望避免在比较时进行转换,那么这可能是为您的比较运算符坚持使用非成员函数模板的好理由。但仅此而已。
从软件工程的角度来看,我认为总是应该尽可能使用自由函数而不是成员方法。我相信所有功能都是如此。为什么?它改进了封装并使函数从“知道”类的实现方式中解放出来。当然,比较函数通常需要访问私有成员,然后使用friend
or 成员函数就可以了(我还是更喜欢friend
)。Scott Meyers 在 Effective C++, item 23 中写了一些关于它的文章