2013-07-02 6 views
5

Considereresti questa prova di cattivo design?È considerato un buon design confrontare oggetti di diverso tipo?

//FooType and BarType not in the same hierarchy 
bool operator==(const FooType &, const BarType &); 
bool operator<(const FooType &, const BarType &); 

Per esempio, se è FooTypedouble secondi di misura dal periodo e BarType è una tupla di tre numeri interi (anno, mese e giorno) data di messa a disposizione in UTC, confronti come sopra "dare un senso".

Avete visto simili confronti tra tipi? Sono disapprovati nella comunità C++?

+3

A seconda di chi manterrà il codice :) Mi piacerebbe implementarlo come metodo di classe, ad esempio BarType :: CompareToEpoch. A proposito, int lungo sarebbe meglio del doppio per misurare i secondi da epoca ... – Alex1985

+0

Penso che la ragione per cui è stata messa in attesa sarà la vostra risposta. – ChiefTwoPencils

+0

Direi di sì, poiché 'operator == (const FooType &)' dovrebbe probabilmente essere un metodo della classe 'BarType', non una funzione. – chepner

risposta

3

visione personale e l'esperienza

io personalmente non disapprovano il confronto tra i diversi tipi. Lo incoraggio persino, poiché potrebbe migliorare la leggibilità del codice; fare ciò che stai facendo sembra più logico. Al di fuori dei tipi di numeri di base, e forse di una stringa e di un personaggio, trovo difficile darti un confronto logico di tipo intra, e non ricordo di averne incontrato molti. Ho incontrato un sacco di operatori aritmetici usati in questo modo però.

come usarli

Si dovrebbe essere cauti con quello che stai facendo, essi sono utilizzati a malapena per un motivo. Se offri una funzione per il confronto di due diversi tipi, il risultato dovrebbe essere logico e ciò che l'utente si aspetta intuitivamente. È anche desiderabile scrivere una buona documentazione per questo. Mark Ransom l'ha già detto, ma è positivo che gli utenti possano confrontarsi in entrambe le direzioni.Se ritieni che il tuo confronto non sia sufficientemente chiaro con un operatore, dovresti pensare a usare una funzione con nome. Questa è anche un'ottima soluzione se il tuo operatore può avere più significati.

cosa può andare storto

Non avete il pieno controllo su ciò che l'utente farà con quello che hai scritto. tletnes ha dato un buon esempio di ciò, dove sono confrontati due interi, ma il risultato non ha significato. In contraddizione con questo, il confronto tra due diversi tipi può essere molto giusto. Un float e un intero che rappresentano entrambi i secondi, possono essere ben confrontati.

operatori aritmetici

vicino al logico, vorrei mostrare un esempio intra-tipo con operatori aritmetici. Gli operatori aritmetici sono molto simili agli operatori logici quando parlano dell'utilizzo intra-tipo.

Supponiamo di avere un operatore + per un vettore a due dimensioni e un quadrato. Cosa fa questo? L'utente potrebbe pensare di ridimensionare il quadrato, ma un altro utente è sicuro che traduca! Questi tipi di problemi possono essere molto frustranti per i tuoi utenti. Puoi risolvere questo problema fornendo una buona documentazione, ma quello che personalmente preferisco, è specificamente chiamato funzioni, come Traduci.

Conclusione

intra-operatori logici di tipo possono essere utili e rendere il codice pulito, ma l'uso cattivo rende tutto solo più complicato.

+1

Una cosa da notare nella sezione "Che cosa può andare storto" è che il numero di confronti supportati per tipi diversi può causare il rapido aumento del numero di funzioni richieste per supportare queste operazioni. 2 tipi, 4 funzioni (a == a, a == b, b == a, b == b). 3 tipi, 9 funzioni. È una complessità N^2 (assumendo proprietà riflessive e simmetriche). – Suedocode

1

Un buon design dovrebbe indicare che è necessario confrontare solo i valori del significato compatibile. Generalmente tipo è un buon indizio al significato, ma non è l'ultima parola, infatti, in molti casi due valori dello stesso tipo possono avere significato incompatibili, come ad esempio i followng due interi:

int seconds = 3 //seconds 
int length = 2; //square inches 
if(seconds >= length){ 
    //what does this mean? 
} 

In questo esempio confronta lunghezza a secondi, tuttavia non c'è una relazione minacciosa tra i due.

int test_duration = 3 //minutes 
float elapsed_time = 2.5; //seconds 
if((test_duration * 60) >= elapsed_time){ 
    //tes is done 
} 

In questo examaple confrontiamo due valori di tipi diversi (e unità) tuttavia le loro significati sono ancora compatibile (entrambi rappresentano tempo) quindi (supponendo che vi sia una buona ragione per cui i due sono stati memorizzati in questo modo (ad esempio facilità di utilizzo di un'API, ecc.)

4

Per cominciare, non c'è niente di sbagliato nell'usare funzioni libere invece di funzioni membro, in realtà è una pratica consigliata.Vedi Scott Meyer's How Non-Member Functions Improve Encapsulation. fornire i confronti in entrambe le direzioni:

bool operator==(const FooType &, const BarType &); 
bool operator==(const BarType &, const FooType &); 

In secondo luogo, è perfettamente accettabile fornire questi confronti se i confronti hanno senso. La libreria standard, ad esempio, consente di confrontare i valori std::complex per l'uguaglianza con virgola mobile, ma non inferiore a.

L'unica cosa che si desidera evitare sono i confronti che non hanno senso. Nel tuo esempio, uno dei valori di tempo è un doppio, il che significa che il confronto si verificherebbe per qualsiasi valore in virgola mobile o intero, una volta che prendi in considerazione le promozioni standard. Questo è probabilmente più di quanto volevi, dal momento che non c'è modo di determinare se un determinato valore rappresenta un tempo. La perdita di controllo del tipo significa che c'è un potenziale per bug non intenzionali.