2015-07-07 12 views
7

Hier ist eine sehr einfache Klassenhierarchie:Wo sollte! = Operator in einer Klassenhierarchie definiert werden?

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

Wie Sie sehen können, Operator = wird in der Basisklasse definiert. Da ich sehr faul bin, möchte ich einen solchen einfachen Code nicht in allen abgeleiteten Klassen duplizieren.

Unfortunatley, mit diesem Code:

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 zurück, weil es führt A::operator!=, welche Anrufe dann A::operator== anstatt B::operator== (auch wenn der Bediener virtuell ist, als abgeleitete Klasse Version Parameter unterschiedlich ist, sind sie nicht in der vtable verknüpft).

Also, was ist der beste Weg zu Adresse! = Operator in einer generischen Weise für eine Klassenhierarchie?

Eine Lösung ist es in jeder Klasse zu wiederholen, würde B dann haben:

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

Aber das ist ein Schmerz, wenn Sie viele Klassen haben .... Ich habe 30 ....

eine andere Lösung wäre, eine generische Vorlage Ansatz haben:

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

Aber dies umgeht alle != Betreiber von jeder Klasse definiert .... so es riskant sein kann, wenn man Decla rot es anders (oder wenn man eine == selbst anruft !=, würde es mit einer Endlosschleife enden ...). Ich halte diese Lösung für sehr unsicher ... außer, wenn wir die Vorlage auf alle Klassen beschränken können, die von der obersten Klasse unserer Hierarchie abgeleitet sind (A in meinem Beispiel) .... aber das tue ich nicht denke, das ist überhaupt machbar.

Hinweis: Ich benutze C++ 11 noch nicht ... tut mir leid.

+0

Darüber hinaus, derzeit, A (42) == B (42, 0) 'wie Sie nur die" A "Teil vergleichen ... – Jarod42

+0

Aus Gründen der Klarheit und um sicherzustellen, dass A weiterhin unabhängig arbeitet (wenn Sie nicht ' t wollen Sie es, Sie müssen B nicht von ihm ableiten), implementieren Sie! = für A, B, C, D und was immer Sie in Ihrer Hierarchie haben. Auch wenn Sie sie nicht brauchen, warum müssen Sie überhaupt ableiten? – Robinson

Antwort

4

Wie wäre es mit so etwas?

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

Bewegen Sie die Vergleichslogik auf eine separate (virtuelle) Funktion equals, und ruft die Funktion aus den operator==operator!= und in der Basisklasse definiert ist.

Die Operatoren müssen in den abgeleiteten Klassen nicht neu definiert werden. Um den Vergleich in einer abgeleiteten Klasse zu ändern, überschreiben Sie einfach equals.

Beachten Sie, dass die dynamic_cast im obigen Code verwendet wird, um sicherzustellen, dass der Laufzeittyp ein gültiger Typ zum Durchführen des Vergleichs ist. Ie. für B::equals, es wird verwendet, um sicherzustellen, dass right ist ein B - dies ist notwendig, weil sonst right würde kein b Mitglied haben.

+2

Damit hast du 'B (42, 0)! = A (42)' aber immer noch 'A (42) == B (42, 0)'. Es würde mehrere Versand erfordern. – Jarod42

+0

Können Sie noch etwas zu Ihrem Ansatz sagen? Danke :-) – Wolf

+0

@ Jarod42: das ist eine Wahl die du machst (und ich kopiere das Verhalten vom OP). Wenn Sie möchten, dass die Gleichheitsoperatoren kommutativ sind, dann kann der doppelte Versand (wie Sie vorschlagen) in der Tat helfen. –

5

Ihre Funktion in B ...

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

... tut nicht Überschreibung der Funktion in A ...

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

... weil die Argumenttypen unterscheiden. (Die Unterschiede sind nur für die kovarianten Rückgabetypen erlaubt.)

Wenn Sie dies zu korrigieren, werden Sie dann in der Lage sein zu entscheiden, wie B Objekte zu anderen A und A abgeleitete Objekten zu vergleichen, zum Beispiel vielleicht:

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

Beachten Sie, dass die Verwendung von typeid Vergleich oben bedeutet nur B Objekte gleich vergleicht: jede B wird ungleich jedem B-abgeleitetes Objekt vergleichen. Dies kann oder kann nicht sein, was Sie wollen.

Mit einer Implementierung für B::operator== wird die vorhandene != Implementierung ordnungsgemäß umhüllen operator==.Wie Jarod42 beobachtet, Ihre A::operator==, dass nicht robust ist, wenn der linke seitige Wert ist eine A nur die A Scheibe des Objekts rechts Seite verglichen wird ... Sie könnten es vorziehen:

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

Dies hat die gleichen Probleme wie die B::operator== oben: z Ein A würde ungleich einem abgeleiteten Objekt vergleichen, das keine weiteren Datenelemente eingeführt hat.

+0

Sind diese dynamic_cast und typeid wirklich sicher (auch bei Verwendung von Template-Klassen, weil meine Klasse hidérachy Vorlagen hat ...)? – jpo38

+0

@ jpo38: Sie sind sicher, ja ... verschiedene RTTI/typeinfo-Objekte werden für jede Template-Instanziierung erstellt. –

+0

Danke. Dein Code funktioniert gut, aber ich bevorzuge die 'Sander De Dycker'-Lösung, weil ich die abgeleitete Klasse (B) bevorzuge, um ein A-Objekt als B zu werfen, anstatt die übergeordnete Klasse (A) zu haben, um das Casting wie vorgeschlagen auszuführen führen zu einem schwer lesbaren Code, wenn die Klassenhierarchie groß ist). – jpo38

Verwandte Themen