我非常支持让编译器为你做尽可能多的工作。当编写一个简单的类时,编译器可以为你提供以下“免费”:
默认(空)构造函数
复制构造函数
一个析构函数
赋值运算符(operator=)
但是它似乎不能给你任何比较操作符——比如operator==或operator!=。例如:
class foo
{
public:
std::string str_;
int n_;
};
foo f1; // Works
foo f2(f1); // Works
foo f3;
f3 = f2; // Works
if (f3 == f2) // Fails
{ }
if (f3 != f2) // Fails
{ }
这有什么好的理由吗?为什么执行逐个成员的比较是一个问题?显然,如果类分配内存,那么你要小心,但对于一个简单的类,编译器肯定可以为你做这个?
即使在c++ 20中,编译器仍然不会隐式地为你生成operator==
struct foo
{
std::string str;
int n;
};
assert(foo{"Anton", 1} == foo{"Anton", 1}); // ill-formed
但是自c++ 20以来,你将获得显式默认==的能力:
struct foo
{
std::string str;
int n;
// either member form
bool operator==(foo const&) const = default;
// ... or friend form
friend bool operator==(foo const&, foo const&) = default;
};
默认==执行成员式==(与默认复制构造函数执行成员式复制构造相同)。新规则还提供了==和!=之间的预期关系。例如,通过上面的声明,我可以写:
assert(foo{"Anton", 1} == foo{"Anton", 1}); // ok!
assert(foo{"Anton", 1} != foo{"Anton", 2}); // ok!
这个特定的特性(默认操作符==以及==和!=之间的对称)来自一个提议,该提议是更广泛的语言特性操作符<=>的一部分。
这有什么好的理由吗?为什么执行逐个成员的比较是一个问题?
这在功能上可能不是问题,但就性能而言,默认的逐个成员的比较可能比默认的逐个成员的赋值/复制更不理想。与赋值顺序不同,比较顺序会影响性能,因为第一个不相等的成员意味着可以跳过其余的成员。如果有一些成员通常是相等的,你想在最后比较它们,而编译器不知道哪些成员更可能是相等的。
考虑这个例子,其中verboseDescription是从一个相对较小的可能的天气描述集中选择的长字符串。
class LocalWeatherRecord {
std::string verboseDescription;
std::tm date;
bool operator==(const LocalWeatherRecord& other){
return date==other.date
&& verboseDescription==other.verboseDescription;
// The above makes a lot more sense than
// return verboseDescription==other.verboseDescription
// && date==other.date;
// because some verboseDescriptions are liable to be same/similar
}
}
(当然,如果编译器意识到比较没有副作用,它就有权忽略比较的顺序,但如果它自己没有更好的信息,它可能仍然会从源代码中获取查询。)