首页 文章

为什么C编译器没有定义operator ==和operator!=?

提问于
浏览
254

我非常喜欢让编译器为你做尽可能多的工作 . 在编写一个简单的类时,编译器可以为“free”提供以下内容:

  • 默认(空)构造函数

  • 复制构造函数

  • 一个析构函数

  • 赋值运算符( 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
{ }

这有充分的理由吗?为什么执行逐个成员比较会出现问题?显然,如果类分配内存然后你要小心,但对于一个简单的类肯定编译器可以为你做这个?

13 回答

  • 36

    编译器不知道您是想要指针比较还是深度(内部)比较 .

    只是不实现它并让程序员自己这样做更安全 . 然后他们可以做出他们喜欢的所有假设 .

  • 279

    该论点认为,如果编译器可以提供默认的复制构造函数,那么它应该能够提供类似的默认值 operator==() 具有一定的意义 . 我认为决定不为此运算符提供编译器生成的默认值的原因可以通过Stroustrup在"The Design and Evolution of C++"中对默认复制构造函数的说法进行猜测(第11.4.1节 - 复制控制):

    我个人认为不幸的是,默认情况下定义了复制操作,我禁止复制我的许多类的对象 . 但是,C从C继承了它的默认赋值和复制构造函数,并且它们经常被使用 .

    因此,而不是“为什么C没有默认 operator==() ?", the question should have been "为什么C有一个默认的赋值和复制构造函数?”,答案是Stroustrup不情愿地包含那些项目以向后兼容C(可能是因为大部分C 's warts, but also probably the primary reason for C++'的人气) .

    出于我自己的目的,在我的IDE中,我用于新类的片段包含私有赋值运算符和复制构造函数的声明,这样当我生成一个新类时,我没有得到默认的赋值和复制操作 - 我必须明确地删除声明如果我希望编译器能够为我生成它们,那么来自 private: 部分的那些操作 .

  • 24

    更新2:不幸的是这个提议didn't make it to C++17,所以现在这方面的语言没有任何改变 .

    更新:该提案的当前版本很有可能被投票到C 17是here .

    有一个recent proposal (N4126)明确默认的比较运算符,它得到了标准委员会非常积极的反馈,所以希望我们能在C 17中以某种形式看到它 .

    简而言之,建议的语法是:

    struct Thing
    {
        int a, b, c;
        std::string d;
    };
    
    bool operator==(const Thing &, const Thing &)= default;
    bool operator!=(const Thing &, const Thing &)= default;
    

    或者以 friend 形式用于具有私有字段的类:

    class Thing
    {
        int a, b;
    
        friend bool operator<(Thing, Thing) = default;
        friend bool operator>(Thing, Thing) = default;
        friend bool operator<=(Thing, Thing) = default;
        friend bool operator>=(Thing, Thing) = default;
    };
    

    或者甚至是简短的形式:

    struct Thing
    {
        int a, b, c;
        std::string d;
    
        default: ==, !=, <, >, <=, >=;   // defines the six non-member functions
    };
    

    当然,这个提案最终被接受的时候可能会改变这一切 .

  • 10

    恕我直言,没有“好”的理由 . 有这么多人同意这个设计决定的原因是因为他们没有学会掌握基于 Value 的语义的力量 . 人们需要编写大量自定义复制构造函数,比较运算符和析构函数,因为它们在实现中使用原始指针 .

    当使用适当的智能指针(如std :: shared_ptr)时,默认的复制构造函数通常很好,假设的默认比较运算符的明显实现也一样好 .

  • 6

    它's answered C++ didn' t = =因为C没有,这就是为什么C在第一个地方只提供default =但是没有== . C希望保持简单:C实现=通过memcpy;但是,由于填充,memcmp无法实现== . 因为padding没有初始化,所以memcmp说它们是不同的,即使它们是相同的 . 空类存在同样的问题:memcmp表示它们不同,因为空类的大小不为零 . 从上面可以看出,实现==比在C中实现=更复杂 . 有些代码example关于此 . 如果我错了,你会得到更正 .

  • 1

    在这个video Alex Stepanov中,STL的创建者在大约13:00解决了这个问题 . 总而言之,看过C的演变他认为:

    • 不幸的是 == and != 没有被隐含地宣布(Bjarne同意他的意见) . 一个正确的语言应该为你准备好这些东西(他进一步建议你不应该定义一个打破 == 语义的 !=

    • 这种情况的原因在C中有其根源(如同很多C问题) . 在那里,赋值运算符是通过逐位赋值隐式定义的,但这对 == 不起作用 . 更详细的解释可以在Bjarne Stroustrup的article中找到 .

    • 在后续问题 Why then wasn't a member by member comparison used 中他说 amazing thing :C是一种本土语言,为Ritchie实施这些东西的人告诉他,他发现这很难实现!

    然后他说,在(遥远的)未来 ==!= 将被隐含地生成 .

  • 54

    无法定义默认 == ,但您可以通过 == 定义默认 != ,您通常应该定义它们 . 为此你应该做以下事情:

    #include <utility>
    using namespace std::rel_ops;
    ...
    
    class FooClass
    {
    public:
      bool operator== (const FooClass& other) const {
      // ...
      }
    };
    

    有关详细信息,请参阅http://www.cplusplus.com/reference/std/utility/rel_ops/ .

    另外,如果定义 operator< ,则在使用 std::rel_ops 时可以从中推导出<=,>,> =的运算符 .

    但是当你使用 std::rel_ops 时应该小心,因为可以推导出比较运算符用于你不期望的类型 .

    从基本推导出相关运算符的更优选方法是使用boost::operators .

    boost中使用的方法更好,因为它为您只想要的类定义运算符的用法,而不是范围内的所有类 .

    您也可以从"+="生成"+", - 从"-="等...(参见完整列表here

  • 7

    C 0x有一个默认函数的提议,所以你可以说 default operator==; 我们已经知道它有助于使这些事情变得明确 .

  • -2

    从概念上讲,定义平等并不容易 . 即使对于POD数据,也可以争辩说即使字段相同,但它是不同的对象(在不同的地址),它也不一定相等 . 这实际上取决于运营商的使用情况 . 不幸的是,你的编译器不是通灵的,也无法推断 .

    除此之外,默认功能是拍摄自己的好方法 . 您描述的默认值基本上是为了保持与POD结构的兼容性 . 然而,它们确实会导致开发人员忘记它们或默认实现的语义 .

  • 41

    C 20提供了一种轻松实现默认比较运算符的方法 .

    cppreference.com中的示例:

    class Point {
        int x;
        int y;
    public:
        auto operator<=>(const Point&) const = default;
        // ... non-comparison functions ...
    };
    // compiler generates all six relational operators
    Point pt1, pt2;
    if (pt1 == pt2) { /*...*/ } // ok
    std::set<Point> s; // ok
    s.insert(pt1); // ok
    if (pt1 <= pt2) { /*...*/ } // ok, makes only a single call to <=>
    
  • 1

    我同意,对于POD类型类,编译器可以为您完成 . 但是你可能认为编译器可能会出错 . 所以最好让程序员去做 .

    我确实有一个POD案例,其中两个字段是唯一的 - 所以比较永远不会被认为是真的 . 然而,我所需要的比较只是在有效载荷上进行比较 - 编译器永远无法理解或者可能无法理解它本身 .

    除此之外 - 他们不需要很长时间才能写出来吗?!

  • 67

    这有充分的理由吗?为什么执行逐个成员比较会出现问题?

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

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

    (当然,如果编译器认识到它们没有副作用,则有权忽略比较的顺序,但可能它仍然会从源代码中获取它没有更好的信息 . )

  • 16

    默认的比较运算符在正确的时间内是正确的;我希望它们会成为问题的根源,而不是有用的东西 .

    此外,您提到的默认方法通常是不受欢迎的 . 看到像这样的代码摆脱默认的复制构造函数和operator =是很常见的:

    class NonAssignable {
    // ....
    private:
        NonAssignable(const NonAssignable&);  // Unimplemented
        NonAssignable& operator=(const NonAssignable&);  // Unimplemented
    };
    

    在很多代码中,通常会看到注释“default copy constructor and operator = OK”,表明它们已被删除或明确定义并不是错误 .

相关问题