C ++比较运算符过载混乱行为

时间:2018-03-06 17:41:14

标签: c++ operator-overloading

我已经实现了两个类(父类和派生类)比较运算符,但测试它我注意到在使用指针的情况下会出现令人困惑的行为。此外,我还有一些关于良好实践的问题。

这是代码:

struct A
{
    int getN() { return _n; }  

    virtual bool equals(A &other) {
        return getN() == other.getN();
    }

    friend bool operator==(const A &one, const A &other);

    A(int n) : _n(n) { }

private:
    int _n;
};

bool operator==(const A &one, const A &other) {
    return one._n == other._n;
}

struct B : public A
{
    friend bool operator==(const B &one, const B &other);

    B(int n, int m = 0) : A(n), _m(m) { }

private:
    int _m;
};

bool operator==(const B &one, const B &other) {
    if( operator==(static_cast<const A&>(one), static_cast<const A&>(other)) ){
        return one._m == other._m;
    } else {
        return false;
    }
}

int main()
{
    A a(10), a2(10);
    B b(10, 20), b2(10, 20), b3(10, 30);

    A *a3 = new B(10, 20);

    bool x1 = a == a2;  // calls A operator (1)
    bool x2 = b == b2;  // calls B operator (2)
    bool x3 = a == b;   // calls A operator (3)
    bool x4 = b == a;   // calls A operator (4)

    bool x5 = b == *a3; // calls A operator (5)

    bool x6 = a == b3;  // calls A operator (6)
    bool x7 = b3 == a;  // calls A operator (7)

    return 0;
}

问题

将A实例与B实例进行比较,调用A类操作符,这是正确的行为吗?

第5点是让我感到困惑的第5点。 a3声明为A,但实例为B,但调用了A类运算符。有什么方法可以解决这个问题吗?

如果操作符是作为实例方法实现的,则根据它使用A对象或B对象调用,执行的方法是不同的。例如:

a == b // executes A::operator==
b == a // executes B::operator==

我认为这是令人困惑且容易出错的,必须避免。我是对的吗?

3 个答案:

答案 0 :(得分:2)

  

A个实例与B个实例进行比较,调用A类运算符,这是正确的行为吗?

是的,因为这是唯一适用的超载。

  

如果运算符是作为实例方法实现的,则根据它使用A对象或B对象调用,执行的方法是不同的。 [...]我认为这是令人困惑和容易出错的,必须避免。

是的,这不是一个好主意,因为相等运算符必须是对称的。尽管可以通过两个独立的运算符对称地实现它,但它在代码中引入了维护责任。

解决此问题的一种方法是扩展您的equals成员函数,并让每个子类为其自己的类型实现相等:

struct A {
    int getN() const { return _n; }  

    virtual bool equals(A &other) const {
        return getN() == other.getN();
    }

    friend bool operator==(const A &one, const A &other);

    A(int n) : _n(n) { }

private:
    int _n;
};

struct B : public A
{
    B(int n, int m = 0) : A(n), _m(m) { }
    virtual bool equals(B &other) const {
        return A::equals(*this, other) && _m == other._m;
    }
private:
    int _m;
};

bool operator==(const A &one, const A &other) {
    return typeid(one)==typeid(two) && one.equals(two);
}

&#34;心脏&#34;此实现的结果是typeid operator,它允许您在运行时检查类型是否相等。只有当one.equals(two)one的类型完全相同时,才会对two进行虚拟调用。

这种方法将责任与阶级本身进行平等比较。换句话说,每个类都需要知道如何比较它自己的实例,并且可选地依赖它的基础来比较它的状态。

答案 1 :(得分:0)

  

A个实例与B个实例进行比较,调用A类运算符,是   这是正确的行为吗?

B&可以转换为const A&,但A&无法转换为const B&。因此,比较a == b实际上只有一个选择。

“班级经营者”不是一件事。在您的代码中,您已将operator==实施为非会员功能,您还成为AB的朋友。

  

第5点是让我感到困惑的第5点。 a3被声明为A   但实例为B,但调用了类A运算符。有没有   解决这个问题的方法?

取消引用a3会返回A&,而非B&

  

如果运算符是作为实例方法实现的,则取决于它   使用A对象或B对象调用,执行的方法是   不同。例如:

a == b // executes A::operator== 
b == a // executes B::operator== 
     

我认为这是令人困惑且容易出错的,必须避免。上午   我没错?

如果将operator==实现为实例方法,则在左侧操作数上调用它。在第一种情况下,它是a,在第二种情况下是b。因此b == a仅在您实施bool B::operator==(const A&)时才有效。

答案 2 :(得分:0)

  

将A实例与B实例进行比较,调用A类操作符,这是正确的行为吗?

由于A中没有任何虚函数,因此只处理function overloading,这意味着编译器决定在编译时调用哪个函数。由B公开继承自AB的指针或引用可以隐式转换为A,但反之亦然。在这种情况下,它意味着“除非两个参数都是B,否则只会调用第一个分辨率”。请注意,当您取消引用指针时,只有指针类型在编译时确定类型很重要,因此如果A *pointer指向AB的实例,则在这种情况下无关紧要,{ {1}}始终具有*pointer类型。

如果您希望根据实际类型调用函数,则需要使用虚函数,可在此处找到A如何执行此操作的详细信息implementing operator== when using inheritance