2015-07-07 14 views
7

Qui è molto semplice gerarchia di classi:Dov'è necessario! = L'operatore deve essere definito in una gerarchia di classi?

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

Come si può vedere, l'operatore = è definito nella classe base. Poiché sono molto pigro, non voglio duplicare un codice così semplice in tutte le classi derivate.

Unfortunatley, con questo codice:

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 restituisce false, perché esegue A::operator!= che chiama poi A::operator== piuttosto che B::operator== (anche se l'operatore è virtuale, come derivato parametro versione della classe è diversa, sono non collegato nel vtable).

Quindi qual è il modo migliore per indicare! = Operatore in modo generico per una gerarchia di classi?

Una soluzione è quella di ripetere in ogni classe, B avrebbero allora:

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

ma questo è un dolore quando si hanno molte classi .... ho 30 ....

un'altra soluzione potrebbe essere quella di avere un approccio modello generico:

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

Ma questo bypassa qualsiasi != operatore definito da ogni classe .... quindi potrebbe essere rischioso se uno Decla rosso in modo diverso (o se uno ha dichiarato uno == stesso chiamando !=, si finirebbe con un ciclo infinito ...). Quindi ritengo che questa soluzione sia molto pericolosa ... eccetto se possiamo limitare il template da utilizzare per tutte le classi derivate dalla classe di livello superiore della nostra gerarchia (A nel mio esempio) .... ma non lo faccio pensa che sia fattibile.

Nota: non sto ancora utilizzando C++ 11 ... mi dispiace.

+0

Inoltre, attualmente, 'A (42) == B (42, 0)' come si confronta solo la parte 'A' ... – Jarod42

+0

Per chiarezza e per garantire che A continua a lavorare in modo indipendente (se non si indossa Lo voglio, non hai bisogno di ricavarne B), implementa! = per A, B, C, D e qualsiasi cosa tu abbia nella tua gerarchia. Ancora una volta se non ne hai bisogno, perché hai bisogno di derivare affatto? – Robinson

risposta

4

Che ne dici di qualcosa del genere?

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

Spostare la logica di confronto di una funzione separata (virtuale) equals e chiamare tale funzione dal operator== e operator!= definito nella classe base.

Gli operatori non devono essere ridefiniti nelle classi derivate. Per modificare il confronto in una classe derivata, è sufficiente eseguire l'override di equals.

Si noti che lo dynamic_cast nel codice sopra riportato viene utilizzato per garantire che il tipo di runtime sia un tipo valido per eseguire il confronto. Vale a dire. per B::equals, viene utilizzato per garantire che right sia un B - questo è necessario perché altrimenti right non avrebbe un membro .

+2

Con questo si ha 'B (42, 0)! = A (42)' ma ancora 'A (42) == B (42, 0)'. Richiederebbe più spedizioni. – Jarod42

+0

Puoi dire qualche parola in più sul tuo approccio? Grazie :-) – Wolf

+0

@ Jarod42: questa è una scelta che fai (e ho copiato il comportamento dall'OP). Se si desidera che gli operatori di uguaglianza siano commutativi, il doppio invio (come suggerito) può effettivamente aiutare. –

5

La vostra funzione in B ...

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

... non non ignorare la funzione in A ...

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

... perché i tipi di argomenti differiscono. (Le differenze sono ammessi solo per i tipi di ritorno covarianti.)

Se si corregge questo, Sarai quindi in grado di scegliere come confrontare B oggetti ad altri A e A oggetti -derived, per esempio forse:

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

noti che utilizzando il confronto typeid sopra significa solo B oggetti si confronta uguale: qualsiasi B confronterà disuguale su qualsiasi oggetto B -derived. Questo può o non può essere quello che vuoi.

Con un'implementazione per B::operator==, l'implementazione != esistente verrà completata correttamente con operator==.Come osserva Jarod42, il vostro A::operator== non è robusto in quanto, quando il valore di sinistra-mano-lato è un A solo il A fetta dell'oggetto destra-mano-lato saranno confrontati ... si potrebbe preferire:

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

Questo ha gli stessi problemi dello B::operator== precedente: ad es. uno A potrebbe confrontare un uguale a un oggetto derivato che non ha introdotto ulteriori membri di dati.

+0

Questi file dynamic_cast e typeid sono davvero sicuri (anche quando si utilizzano classi template, perché la mia classe hirerachy ha template ....)? – jpo38

+0

@ jpo38: sono sicuri, sì ... distinti oggetti RTTI/typeinfo verranno creati per ogni istanza di modello. –

+0

Grazie. Il tuo codice funziona bene, ma preferisco la soluzione 'Sander De Dycker' perché preferisco la classe derivata (B) per lanciare un oggetto A come B piuttosto che avere la classe genitore (A) da trasmettere per eseguire il cast come hai proposto (potrebbe portare a un codice difficile da leggere se la gerarchia delle classi è enorme). – jpo38