2012-01-26 10 views
9

Sto provando a confrontare gli oggetti di una classe base comune insieme. Il confronto dovrebbe fallire (in uscita una stringa di errore, ad esempio) in ogni caso quando i due oggetti differiscono in classe, o differiscono in valori specifici per l'oggetto. Idealmente il confronto è in qualche modo applicato, così che una nuova classe derivata dovrebbe anche scrivere una funzione di confronto per i membri della sua classe. Ecco un esempio di codice:Confronto di classi derivate in C++ senza fusione dinamica o downcast statico

#include <iostream> 
#include <string> 
#include <vector> 

class Vehicle 
{ 
public: 
    virtual std::string compareTo(Vehicle* v) = 0; 
}; 

class Bicycle : public Vehicle 
{ 
public: 
    Bicycle() { color_ = "red"; } 
    std::string compareTo(Vehicle* v) { return "We're different vehicles."; } 
    std::string compareTo(Bicycle* b) { return color_.compare(b->color_) ? "We're different bicycles." : "We're the same bicycle."; } 

private: 
    std::string color_; 
}; 

class Car : public Vehicle 
{ 
public: 
    Car() { style_ = "sedan"; } 
    std::string compareTo(Vehicle* v) { return "We're different vehicles."; } 
    std::string compareTo(Car* c) { return style_.compare(c->style_) ? "We're different cars." : "We're the same car."; } 

private: 
    std::string style_; 
}; 

int main() 
{ 
    Vehicle* compareFrom = new Bicycle(); 

    std::vector<Vehicle*> compareTos; 
    compareTos.push_back(new Bicycle()); 
    compareTos.push_back(new Car()); 

    std::vector<Vehicle*>::iterator it; 
    for (it = compareTos.begin(); it != compareTos.end(); ++it) 
    std::cout << compareFrom->compareTo(*it) << std::endl; 

    return 0; 
} 

Attualmente, l'uscita (che potete vedere here) dice: "Siamo diversi veicoli". So che questo sta accadendo perché sto usando il puntatore di base astratto. Il problema è come risolverlo!

L'output che avrei come da avere è che le biciclette emettono che sono uguali, perché hanno lo stesso colore. Le biciclette e le auto dovrebbero produrre che sono veicoli diversi. Anche le biciclette di diversi colori e le auto di stili diversi devono mostrare che sono diverse. Mi sembra che ci debba essere un ottimo schema da utilizzare per risolvere questo problema, ma mi sto impantanando nel casting dinamico o in problemi di downcast non sicuri. Inoltre, vorrei che la funzione di confronto fosse applicata tra membri della stessa classe (quindi le biciclette devono essere in grado di confrontarsi con altre biciclette).

+0

Mi dispiace, nota che string.compare() restituisce 0 quando le stringhe corrispondono. –

+0

Perché ti consideri 'impantanato' nel casting dinamico? – Macke

+0

@Macke Da quello che ho letto, il casting dinamico non dovrebbe essere necessario in una "corretta" progettazione orientata agli oggetti. Dal momento che sto facendo questo progetto da zero, volevo farlo bene! – aardvarkk

risposta

11

Si desidera Multiple Dispatch (ad esempio, selezionare la funzione da chiamare in modo dinamico in base a più di una variabile, non solo "questo"). Questo perché è necessario ispezionare il tipo in qualche modo, altrimenti il ​​compilatore eseguirà un'analisi statica sui tipi e selezionerà quale funzione chiamare (quella virtuale in Veicolo).

Niente da fare. dynamic_cast è tuo amico qui, ma potresti volere rotolare il tuo sistema RTTI per motivi di prestazioni (o di altro tipo). (L'articolo di Wikipedia mostra un modo ..)

std::string Bicycle::compareTo(Vehicle* v) { 
    if (Bicycle* b = dynamic_cast<Bicycle*>(v)) { 
     return compareTo(b); 
    } else { 
     return "We're different vehicles."; 
    } 
} 

C'è un'implementazione di questo modello nel Loki C++ library che potrebbe aiutare se si dispone di molti tipi che hanno bisogno il confronto.

La spedizione multipla non è supportata dalla lingua in C++, né nella maggior parte delle lingue tradizionali. C'era una proposta per aggiungerlo a C++ 11, vedi questo question e Bjarne's paper. Penso che sia stato respinto perché i problemi (noti e sconosciuti) con il collegamento dinamico, di cui lo standard C++ non sa nulla.

+0

Il problema che ho riscontrato con dispatch multipli è che apparentemente si deve implementare una funzione per ogni altro tipo da confrontare. Ad esempio, se avessi 100 tipi di veicoli, ciascuna sottoclasse di veicoli dovrebbe essere in grado di confrontarsi con un'altra. Sembra anche che se * dimentichi * di implementare qualsiasi, corri in un potenziale loop infinito. Questo approccio dinamico lo evita bene, ho solo pensato che forse c'era qualcosa di super-intelligente che mi mancava. – aardvarkk

+0

@aardvarkk: Beh, la cosa super-intelligente non è supportata da C++, quindi è necessario aggirarla. Il file di intestazione di Loki aiuta ad automatizzare le cose noiose. – Macke

+0

@aardvarkk: Inoltre, se si dispone di 100 tipi di classi di veicoli che richiedono un codice di confronto personalizzato, è probabilmente necessario ripensare il proprio progetto. ;-) Tuttavia, 10 diversi tipi di geometria che richiedono il rilevamento delle collisioni non è raro, e quindi è necessario codice specializzato in ogni versione per essere efficiente. – Macke

0

Se RTTI è abilitato nel compilatore, è possibile utilizzare l'operatore typeid(), ma ciò richiede che le classi siano polimorfiche.

5

Il tuo codice ha il grosso problema che non è facilmente estensibile (viola lo open/closed principle). È tuttavia possibile delegare il confronto a un metodo di classe base.

Inoltre, se si vuole applicare la semantica (una buona cosa), allora non sarà possibile aggirare il downcasting, mi spiace.

Per rendere più robusta ed estensibile,

  1. rendere il metodo di base pura virtuale
  2. fornire un'implementazione per il metodo di base (sì, questo funziona! Anche se è virtuale pura) che mette a confronto gli oggetti tipi
  3. Nelle classi derivate, utilizzare l'implementazione della classe base per verificare l'uguaglianza di tipo, quindi eseguire il controllo logico effettivo.
#include <iostream> 
#include <iomanip> 
#include <string> 
#include <typeinfo> 

struct vehicle { 
    virtual bool compare_to(vehicle const& other) const = 0; 
}; 

bool vehicle::compare_to(vehicle const& other) const { 
    return typeid(*this) == typeid(other); 
} 

struct car : vehicle { 
    std::string color; 

    car(std::string const& color) : color(color) { } 

    bool compare_to(vehicle const& other) const { 
     bool result = vehicle::compare_to(other); 
     return result and (color == static_cast<car const&>(other).color); 
    } 
}; 

struct bike : vehicle { 
    int spokes; 

    bike(int spokes) : spokes(spokes) { } 

    bool compare_to(vehicle const& other) const { 
     bool result = vehicle::compare_to(other); 
     return result and (spokes == static_cast<bike const&>(other).spokes); 
    } 
}; 

int main() { 
    car c1("blue"); 
    car c2("red"); 
    bike b1(42); 

    std::cout << std::boolalpha; 
    std::cout << c1.compare_to(c2) << "\n" 
       << c1.compare_to(b1) << "\n" 
       << c1.compare_to(c1) << "\n"; 
} 

Il codice sopra, la static_cast è sicuro poiché abbiamo assicurato in anticipo che il tipo è lo stesso, così il getto sarà mai sicuro.

Nota che l'uso di typeid qui è del tutto legittimo. Non dovrebbe nemmeno essere molto inefficiente dal momento che non esiste una gerarchia di tipo profondo da percorrere. Ma se vuoi renderlo più efficiente, puoi implementare un semplice meccanismo che utilizza una tabella statica nella classe base per mappare ogni istanza creata per identificarne il numero univoco (es. std::map<vehicle*, type_id>, dove type_id è un semplice vecchio enum) ed eseguire una semplice ricerca

... oppure utilizzare dynamic_cast, in realtà.

+0

Grazie per la tua risposta completa! Sembra che RTTI sia necessario qui ... – aardvarkk

+0

@AndyT Si prega di non "correggere" la sintassi, era già corretta. 'and' e' or' sono validi token di preelaborazione alternativi per '&&' e '||', anche se Microsoft finge in modo diverso. –

+0

@KonradRudolph: mi dispiace, non lo sapevo –

4

Normalmente lo implemento utilizzando un membro di tipo nella classe base. Trovo questo ha un paio di vantaggi:

  • prestazioni - senza bisogno di chiamata di funzione virtuale e dinamico cast
  • Utilizzando un diverso 'po' per ogni tipo di classe, i confronti di livello quindi più elevati possono essere fatte. Ad esempio "monociclo" e "bicicletta" potrebbero essere entrambi alimentati dall'uomo, quindi potresti facilmente verificarlo separatamente dal loro tipo principale.

Il tipo tipo sarà simile alla seguente:

enum Kind { 
     HUMAN_POWERED = (0x1 << 0), 
     MOTOR_POWERED = (0x1 << 1), 
     BICYCLE  = (0x1 << 2) | HUMAN_POWERED, 
     UNICYCLE  = (0x1 << 3) | HUMAN_POWERED, 
     CAR   = (0x1 << 4) | MOTOR_POWERED 
    }; 

Ora è possibile verificare che la macchina non è una bicicletta, ma anche se sono due tipi MOTOR_POWERED o no!

bool areSameClass (Vehicle const & lhs, Vehicle const & rhs) 
{ 
    return (lhs->getKind() & rhs->getKind()) & (HUMAN_POWERED | MOTOR_POWERED); 
} 
Problemi correlati