2015-07-07 13 views
7

Tutaj jest bardzo prosta klasa hierarchia:Gdzie powinien! = Operator być zdefiniowany w hierarchii klas?

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

Jak widać, operator = jest zdefiniowana w klasie bazowej. Ponieważ jestem bardzo leniwy, nie chcę duplikować takiego prostego kodu we wszystkich klasach pochodnych.

Unfortunatley, z tym kodem:

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 zwraca false, ponieważ wykonuje A::operator!= który wywołuje następnie A::operator== zamiast B::operator== (nawet jeśli operator wirtualny, gdyż pochodzą parametr wersja klasa jest inna, są one nie połączone w vtable).

Jaki jest najlepszy sposób na adres! = Operator w sposób ogólny do hierarchii klas?

Jednym z rozwiązań jest powtarzanie go w każdej klasie, B musiałaby wówczas:

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

Ale to ból, gdy masz wiele klas .... Mam 30 ....

Innym rozwiązaniem byłoby mieć ogólne podejście szablon:

template <class T> 
bool operator!=(const T& left, const T& right) 
{ 
    return !(left == right); 
} 

Ale ta omija żadnej != operatora zdefiniowanego przez jakiejkolwiek klasy .... więc może to być ryzykowne, jeśli jeden decla czerwony to inaczej (lub jeśli zadeklarowano, że == sam wywołuje !=, skończyłoby się nieskończoną pętlą ...). Czuję, że to rozwiązanie jest bardzo niebezpieczne ... chyba że możemy ograniczyć szablon do użycia dla wszystkich klas wywodzących się z klasy najwyższego poziomu naszej hierarchii (A w moim przykładzie) ... ale ja nie myślę, że jest to w ogóle wykonalne.

Uwaga: nie używam jeszcze C++ 11 ... przepraszam za to.

+0

Co więcej, obecnie "A (42) == B (42, 0)", porównując tylko część "A" ... – Jarod42

+0

Dla przejrzystości i zapewnienia, że ​​A nadal działa niezależnie (jeśli nie t chcesz tego, nie musisz wywodzić z niego B), implementuj! = dla A, B, C, D i cokolwiek masz w swojej hierarchii. Znowu, jeśli ich nie potrzebujesz, dlaczego w ogóle musisz czerpać? – Robinson

Odpowiedz

4

Co powiesz na coś takiego?

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

Przenoszenie logikę porównaniu z osobnym (wirtualnych) Funkcja equals i wywołać tę funkcję z operator== i operator!= zdefiniowane w klasie bazowej.

Operatory nie muszą być ponownie definiowane w klasach pochodnych. Aby zmienić porównanie w klasie pochodnej, wystarczy zastąpić equals.

Należy pamiętać, że kod dynamic_cast w powyższym kodzie służy do upewnienia się, że typ środowiska wykonawczego jest poprawnym typem do wykonywania porównania. To znaczy. dla B::equals, jest to używane w celu zapewnienia, że ​​right jest - jest to konieczne, ponieważ w przeciwnym razie right nie miałby członka b.

+2

Z tym masz 'B (42, 0)! = A (42)' ale nadal 'A (42) == B (42, 0)'. Wymagałoby to wielokrotnej wysyłki. – Jarod42

+0

Czy możesz powiedzieć więcej o swoim podejściu? Dzięki :-) – Wolf

+0

@ Jarod42: to jest twój wybór (i skopiowałem zachowanie z OP). Jeśli chcesz, aby operatory równości były przemienne, to podwójna wysyłka (jak sugerujesz) może naprawdę pomóc. –

5

Twoja funkcja w B ...

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

... robi nie przesłonić funkcję w A ...

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

... bo typy argumentów różnią. (Różnice są dozwolone tylko w przypadku kowariancyjnych typów zwracanych).

Jeśli to poprawisz, będziesz mógł wybrać sposób porównywania obiektów B z innymi obiektami, np. A i A. może:

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

Należy pamiętać, że stosując powyższy porównania typeid oznacza tylko B obiekty będą porównać równe: każdy B porówna nierówne do dowolnego B pochodzące z A obiektu. To może, ale nie musi być to, co chcesz.

Z implementacją dla B::operator==, istniejąca implementacja != odpowiednio opakuje operator==.Jak zauważa Jarod42, twój A::operator== nie jest mocna, że ​​gdy wartość lewej stronie jest tylko AA plaster obiektu po prawej stronie zostaną porównane ... może wolisz:

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

To ma te same problemy co powyżej: B::operator==: np. A będzie porównywać wartość równą obiektowi pochodnemu, który nie wprowadził dalszych elementów danych.

+0

Czy te dynamic_cast i typeid są naprawdę bezpieczne (nawet przy korzystaniu z klas szablonów, ponieważ moja aplikacja classrachy ma szablony ....)? – jpo38

+0

@ jpo38: są bezpieczne, tak ... różne obiekty RTTI/typeinfo zostaną utworzone dla każdej instancji szablonu. –

+0

Dzięki. Twój kod działa dobrze, ale ja wolę rozwiązanie "Sander De Dycker", ponieważ wolę klasę pochodną (B), aby rzutować obiekt A jako B zamiast mieć klasę nadrzędną (A) do rzucania, aby wykonać rzut zgodnie z twoją propozycją (może to być prowadzić do trudnego do odczytania kodu, jeśli hierarchia klasowa jest ogromna). – jpo38

Powiązane problemy