我非常支持让编译器为你做尽可能多的工作。当编写一个简单的类时,编译器可以为你提供以下“免费”:

默认(空)构造函数 复制构造函数 一个析构函数 赋值运算符(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开始,它可以通过命令auto操作符自动生成<=>(const foo&) const = default;

它将生成所有的运算符:==,!=,<,<=,>和>=,详细信息请参见https://en.cppreference.com/w/cpp/language/default_comparisons。

由于操作员的外观<=>,它被称为宇宙飞船操作员。另见为什么在c++中我们需要太空船<=>操作符?

EDIT:同样在c++ 11中,std::tie也提供了一个相当简洁的替代品,有关bool操作符<(…)的完整代码示例,请参阅https://en.cppreference.com/w/cpp/utility/tuple/tie。有趣的部分,改变为使用==是:

#include <tuple>

struct S {
………
bool operator==(const S& rhs) const
    {
        // compares n to rhs.n,
        // then s to rhs.s,
        // then d to rhs.d
        return std::tie(n, s, d) == std::tie(rhs.n, rhs.s, rhs.d);
    }
};

tie适用于所有比较操作符,并被编译器完全优化。

其他回答

即使在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!

这个特定的特性(默认操作符==以及==和!=之间的对称)来自一个提议,该提议是更广泛的语言特性操作符<=>的一部分。

我同意,对于POD类型的类,编译器可以为你做。然而,你可能认为简单的编译器可能会出错。所以最好还是让程序员来做。

我曾经有一个POD案例,其中两个字段是唯一的-所以比较永远不会被认为是正确的。然而,我所需要的比较只是在有效负载上进行比较——这是编译器永远无法理解或自己无法解决的问题。

此外,他们不需要花很长时间来写,不是吗?!

如果编译器可以提供默认的复制构造函数,它就应该能够提供类似的默认操作符==(),这在一定程度上是有意义的。我认为决定不为该操作符提供编译器生成的默认构造函数的原因可以从Stroustrup在“c++的设计和发展”(第11.4.1节-复制的控制)中关于默认复制构造函数的描述中猜到:

我个人认为这很不幸 定义的复制操作 默认和我禁止复制 我的许多类的对象。 然而,c++继承了它的默认值 赋值和复制构造函数 C,它们经常被使用。

因此,问题不应该是“为什么c++没有默认的操作符==()?”,而应该是“为什么c++有默认的赋值和复制构造函数?”,答案是Stroustrup为了向后兼容C而不情愿地包含了这些项(可能是c++的大部分缺点的原因,但也可能是c++流行的主要原因)。

出于我自己的目的,在我的IDE中,我用于新类的代码片段包含私有赋值操作符和复制构造函数的声明,因此当我生成一个新类时,我没有得到默认的赋值和复制操作——如果我想让编译器能够为我生成这些操作,我必须显式地从private:节中删除这些操作的声明。

这有什么好的理由吗?为什么执行逐个成员的比较是一个问题?

这在功能上可能不是问题,但就性能而言,默认的逐个成员的比较可能比默认的逐个成员的赋值/复制更不理想。与赋值顺序不同,比较顺序会影响性能,因为第一个不相等的成员意味着可以跳过其余的成员。如果有一些成员通常是相等的,你想在最后比较它们,而编译器不知道哪些成员更可能是相等的。

考虑这个例子,其中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
    }
}

(当然,如果编译器意识到比较没有副作用,它就有权忽略比较的顺序,但如果它自己没有更好的信息,它可能仍然会从源代码中获取查询。)

这样,随着时间的推移,这个问题的答案仍然是完整的:从c++ 20开始,它可以通过命令auto操作符自动生成<=>(const foo&) const = default;

它将生成所有的运算符:==,!=,<,<=,>和>=,详细信息请参见https://en.cppreference.com/w/cpp/language/default_comparisons。

由于操作员的外观<=>,它被称为宇宙飞船操作员。另见为什么在c++中我们需要太空船<=>操作符?

EDIT:同样在c++ 11中,std::tie也提供了一个相当简洁的替代品,有关bool操作符<(…)的完整代码示例,请参阅https://en.cppreference.com/w/cpp/utility/tuple/tie。有趣的部分,改变为使用==是:

#include <tuple>

struct S {
………
bool operator==(const S& rhs) const
    {
        // compares n to rhs.n,
        // then s to rhs.s,
        // then d to rhs.d
        return std::tie(n, s, d) == std::tie(rhs.n, rhs.s, rhs.d);
    }
};

tie适用于所有比较操作符,并被编译器完全优化。