C++ 你应该去哪里运算符是否可以在类层次结构中定义?

C++ 你应该去哪里运算符是否可以在类层次结构中定义?,c++,operator-overloading,comparison-operators,C++,Operator Overloading,Comparison Operators,下面是一个非常简单的类层次结构: class A { public: A( int _a ) : a( _a ) {} virtual bool operator==( const A& right ) const { return a == right.a; } virtual bool operator!=( const A& right ) const { return !( *this =

下面是一个非常简单的类层次结构:

class A
{
public:
    A( int _a ) : a( _a ) {}

    virtual bool operator==( const A& right ) const
    {
        return a == right.a;
    }

    virtual bool operator!=( const A& right ) const
    {
        return !( *this == right );
    }

    int a;
};

class B : public A
{
public:
    B( int _a, int _b ) : A( _a ), b( _b ) {}

    virtual bool operator==( const B& right ) const
    {
        return A::operator==( right ) && b == right.b;
    }

    int b;
};
如您所见,接线员!=是在基类中定义的。因为我很懒,所以我不想在所有派生类中重复这样一个简单的代码

不幸的是,使用此代码:

A a4(4), a5(5), a4bis(4);
assert( a4 == a4bis );
assert( a4 != a5 );

B b1(4,5), b2(4,6);
assert( !(b1 == b2) );
assert( b1 != b2 ); // fails because B::operator== is not called!
b1!=b2
返回false,因为它执行
A::operator=
然后调用
A::operator=
,而不是
B::operator==
(即使该操作符是虚拟的,由于派生类版本参数不同,它们也不会在vtable中链接)

那么,最好的地址是什么类层次结构的通用方式的运算符

一种解决方案是在每个类中重复它,
B
将具有:

virtual bool operator!=( const B& right ) const
{
    return !( *this == right );
}
但是当你有很多课的时候,那是一种痛苦…我有30

另一个解决方案是采用通用模板方法:

template <class T>
bool operator!=( const T& left, const T& right )
{
    return !( left == right );
}
模板
接线员=(常数T和左、常数T和右)
{
返回!(左==右);
}
但这绕过了任何
=运算符…因此,如果以不同的方式声明它(或者如果声明一个
=
本身调用
!=
,它将以无限循环结束…)可能会有风险。所以我觉得这个解决方案非常不安全……除非我们可以限制模板用于从我们层次结构的顶级类派生的所有类(
A
,在我的示例中)……但我认为这根本不可行


注意:我还没有使用C++11…很抱歉。

您在
B
中的函数

virtual bool operator==( const B& right ) const
virtual bool operator==( const A& right ) const
…不会覆盖
A
中的功能

virtual bool operator==( const B& right ) const
virtual bool operator==( const A& right ) const
…因为参数类型不同。(差异仅允许用于协变返回类型。)

如果您纠正了这一点,您将能够选择如何将
B
对象与其他
A
A
派生对象进行比较,例如:

bool operator==( const A& right ) const override
{
    if (A::operator==( right ))
        if (typeid(*this) == typeid(right))
            return b == static_cast<const B&>(right).b;
    return false;
}

这与上面的
B::operator==
存在相同的问题:例如
A
会将unequal与没有引入更多数据成员的派生对象进行比较。

类似的情况如何

class A {
  protected :
    virtual bool equals(const A& right) const {
      return (a == right.a);
    }

  public :
    A(int _a) : a(_a) { }

    bool operator==(const A& right) const {
      return this->equals(right);
    }
    bool operator!=(const A& right) const {
      return !(this->equals(right));
    }

    int a;
};

class B : public A {
  protected :
    virtual bool equals(const A& right) const {
      if (const B* bp = dynamic_cast<const B*>(&right)) {
        return A::equals(right) && (b == bp->b);
      }
      return false;
    }

  public :
    B(int _a, int _b) : A(_a), b(_b) { }

    int b;
};
A类{
受保护的:
虚拟布尔等于(常数A和右)常数{
返回值(a==右.a);
}
公众:
A(int_A):A(_A){}
布尔运算符==(常量A和右侧)常量{
返回此->等于(右);
}
布尔运算符!=(常量A和右侧)常量{
返回!(此->等于(右));
}
INTA;
};
B类:公共A{
受保护的:
虚拟布尔等于(常数A和右)常数{
if(常量B*bp=动态_投射(&R)){
返回A::等于(右)&&(b==bp->b);
}
返回false;
}
公众:
B(int_a,int_B):a(_a),B(_B){
int b;
};
将比较逻辑移动到一个单独的(虚拟)函数
equals
,并从
操作符==
操作符调用该函数=在基类中定义

这些运算符不需要在派生类中重新定义。要更改派生类中的比较,只需重写
equals


请注意,上面代码中的
dynamic_cast
用于确保运行时类型是执行比较的有效类型。例如,对于
B::equals
,它用于确保
right
B
-这是必要的,因为否则
right
将不会有
B
成员。

此外,目前,
a(42)==B(42,0)
当您仅比较
A
部分时……为了清晰起见,并确保A继续独立工作(如果您不希望,您不需要从中派生B),请执行!=对于A,B,C,D和你们等级体系中的任何东西。同样,如果你不需要它们,为什么你需要派生呢?有了它们,你就有了
B(42,0)!=A(42)
但仍然
A(42)==B(42,0)
。这需要多次调度。你能再多说几句你的方法吗?谢谢:-)@Jarod42:这是你的选择(我从OP中复制了这个行为)。如果您希望相等运算符是可交换的,那么双重分派(如您所建议的)确实会有所帮助。谢谢!每个类都可以覆盖第三个
等于
的函数,这是解决我问题的好方法……根据双重分派:我对答案有一种不好的感觉,但没有任何警告——问题是在层次结构中不同(
!=
)。这些动态转换和类型ID真的安全吗(即使在使用模板类时,因为我的类Hirrachy有模板…)@jpo38:它们是安全的,是的…每个模板实例化都会创建不同的RTTI/typeinfo对象。谢谢。您的代码运行良好,但我更喜欢
Sander De Dycker
解决方案,因为我更喜欢派生类(B)将a对象强制转换为B,而不是按照您的建议使用父类(a)进行强制转换(如果类层次结构庞大,可能会导致代码难以阅读)。@jpo38:我没有查看他的代码进行比较,而是根据您提到的具体标准—“而不是使用父类(a)“按你的建议做演员”-我根本没有提议…上面唯一的强制转换是在
B::operator=
中,它将其他
B
对象强制转换到
B&
以访问它们的
B
成员。也许你把该代码误认为是
A::operator=
覆盖
显示它是用于
B
。好的,你完全正确,所以谢谢。反正我都投了你们两个的票;-)。谢谢