2015-07-07 4 views
7

Вот очень простая иерархия классов:Где должен? = Оператор быть определен в иерархии классов?

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; 
}; 

Как вы можете видеть, оператор = определен в базовом классе. Поскольку я очень ленив, я не хочу дублировать такой простой код во всех производных классах.

Unfortunatley, с этим кодом:

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 возвращает ложь, потому что он выполняет 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); 
} 

Но это обходит любой != оператор, определяемый любым классом .... так что это может быть опасно, если один decla по-другому (или если объявлено ==, вызывающее !=, это закончится бесконечным циклом ...). Поэтому я считаю, что это решение очень опасно ... за исключением того, что мы можем ограничить шаблон, который будет использоваться для всех классов, полученных из класса верхнего уровня нашей иерархии (A в моем примере) .... но я не думаю, что это выполнимо вообще.

Примечание: Я не использую C++ 11, но ... извините за это.

+0

Кроме того, в настоящее время 'A (42) == B (42, 0)', поскольку вы сравниваете только часть 'A' ... – Jarod42

+0

Для большей ясности и обеспечения того, чтобы A продолжал работать независимо (если вы не используете" t хочу, вам не нужно выводить B из него), реализовать! = для A, B, C, D и того, что у вас есть в вашей иерархии. Опять же, если они вам не нужны, зачем вам вообще вообще выходить? – Robinson

ответ

4

Как насчет чего-то подобного?

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; 
}; 

Переместить логику сравнения в отдельные (виртуальные) функции equals, и называет эту функцию из operator== и operator!=, определенных в базовом классе.

Операторы не обязательно должны быть переопределены в производных классах. Чтобы изменить сравнение в производном классе, просто переопределите equals.

Обратите внимание, что dynamic_cast в приведенном выше коде используется для обеспечения допустимого типа выполнения для выполнения сравнения. То есть. для B::equals, он используется для обеспечения того, чтобы right был B - это необходимо, потому что в противном случае right не был бы членом b.

+2

При этом у вас есть 'B (42, 0)! = A (42)', но все еще 'A (42) == B (42, 0)'. Это потребует многократной отправки. – Jarod42

+0

Можете ли вы сказать несколько слов о своем подходе? Спасибо :-) – Wolf

+0

@ Jarod42: это выбор, который вы делаете (и я скопировал поведение из OP). Если вы хотите, чтобы операторы равенства были коммутативными, то двойная отправка (по вашему мнению) действительно может помочь. –

5

Ваша функция в B ...

virtual bool operator==(const B& right) const 

... делает не переопределение функции в A ...

virtual bool operator==(const A& right) const 

... потому что типы аргументов отличаются. (Различия допускаются только для ковариантных типов возврата.)

Если вы исправить это, вы затем сможете выбрать, как сравнивать B объектов других A и A -derived объектов, например, возможно:

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; 
} 

Обратите внимание, что с помощью typeid сравнения выше означает только B объекты считаются равными: любая B сравнит неравную любой B -derived объекта. Это может быть или не быть тем, что вы хотите.

С реализацией для B::operator== существующая реализация != будет правильно обертывать operator==.Как отмечает Jarod42, ваш A::operator== не является устойчивой, в том, что, когда значение левой стороны является A только A среза правой стороны объекта будет сравниваться ... Вы могли бы предпочесть:

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

У этого продукта такие же проблемы, как и у B::operator==: eg a A будет сравнивать неравномерность с производным объектом, который не вводит дополнительные члены данных.

+0

Действительно ли эти dynamic_cast и typeid действительно безопасны (даже при использовании классов шаблонов, потому что у моего класса hirerachy есть шаблоны ...)? – jpo38

+0

@ jpo38: они безопасны, да ... для каждого экземпляра шаблона будут созданы различные объекты RTTI/typeinfo. –

+0

Спасибо. Ваш код работает, но я предпочитаю решение Sander De Dycker, потому что я предпочитаю, чтобы производный класс (B) использовал объект A как B, вместо того чтобы иметь родительский класс (A), который нужно выполнить, чтобы сделать кастинг, как вы предложили (это может приводят к тому, что код трудно читать, если иерархия классов огромна). – jpo38

Смежные вопросы