C ++ 20 ha introdotto confronti predefiniti, alias "astronave"operator<=>
, che consente di richiedere <
/ <=
/ ==
/ !=
/ >=
/ e / o >
operatori generati dal compilatore con l'implementazione ovvia / ingenua (?) ...
auto operator<=>(const MyClass&) const = default;
... ma puoi personalizzarlo per situazioni più complicate (discusso di seguito). Vedi qui per la proposta linguistica, che contiene giustificazioni e discussioni. Questa risposta rimane rilevante per C ++ 17 e versioni precedenti e per informazioni su quando è necessario personalizzare l'implementazione di operator<=>
....
Può sembrare un po 'inutile per C ++ non averlo già standardizzato in precedenza, ma spesso le strutture / classi hanno alcuni membri di dati da escludere dal confronto (ad esempio contatori, risultati memorizzati nella cache, capacità del contenitore, codice di errore / successo dell'ultima operazione, cursori), come così come le decisioni da prendere su una miriade di cose tra cui ma non limitate a:
- quali campi confrontare per primi, ad esempio il confronto di un particolare
int
membro potrebbe eliminare il 99% di oggetti disuguali molto rapidamente, mentre un map<string,string>
membro potrebbe spesso avere voci identiche ed essere relativamente costoso da confrontare - se i valori vengono caricati in fase di esecuzione, il programmatore potrebbe avere intuizioni il compilatore non è possibile
- nel confronto di stringhe: distinzione tra maiuscole e minuscole, equivalenza di spazi e separatori, convenzioni di escape ...
- precisione quando si confrontano float / double
- se i valori in virgola mobile NaN debbano essere considerati uguali
- confrontare puntatori o dati puntati (e in quest'ultimo caso, come sapere se i puntatori sono agli array e di quanti oggetti / byte necessitano di confronto)
- se l'ordine è importante quando si confrontano contenitori non ordinati (ad es
vector
. list
) e, in tal caso, se è ok ordinarli sul posto prima del confronto rispetto all'uso di memoria extra per ordinare i provvisori ogni volta che viene eseguito un confronto
- quanti elementi dell'array attualmente contengono valori validi che dovrebbero essere confrontati (c'è una dimensione da qualche parte o una sentinella?)
- quale membro di
union
a confrontare
- normalizzazione: ad esempio, i tipi di data possono consentire un giorno del mese o un mese dell'anno fuori intervallo, oppure un oggetto razionale / frazione può avere 6/8 mentre un altro ha 3/4, che per motivi di prestazioni correggono pigramente con un passaggio di normalizzazione separato; potresti dover decidere se attivare una normalizzazione prima del confronto
- cosa fare quando i puntatori deboli non sono validi
- come gestire membri e basi che non si implementano da
operator==
soli (ma potrebbero avere compare()
or operator<
or str()
or getter ...)
- quali blocchi devono essere presi durante la lettura / confronto dei dati che altri thread potrebbero voler aggiornare
Quindi, è un po ' bello avere un errore finché non hai pensato esplicitamente a cosa dovrebbe significare il confronto per la tua struttura specifica, piuttosto che lasciarlo compilare ma non darti un risultato significativo in fase di esecuzione .
Detto questo, sarebbe bello se C ++ ti permettesse di dire bool operator==() const = default;
quando hai deciso che un ==
test "ingenuo" membro per membro era ok. Lo stesso per !=
. Attribuite più membri / basi, "default" <
, <=
, >
, e >=
le implementazioni sembrano senza speranza anche se - a cascata sulla base dell'ordine di tutto è possibile, ma molto improbabile che sia quello che voleva, dato in conflitto imperativi per gli ordini (basi essendo necessariamente prima che i membri, il raggruppamento mediante dichiarazione accessibilità, costruzione / distruzione prima dell'uso dipendente). Per essere più ampiamente utile, il C ++ avrebbe bisogno di un nuovo sistema di annotazione di base / membro dei dati per guidare le scelte - sarebbe un'ottima cosa avere nello Standard, tuttavia, idealmente accoppiato con la generazione di codice definita dall'utente basata su AST ... esso '
Tipica implementazione degli operatori di uguaglianza
Un'attuazione plausibile
È probabile che un'implementazione ragionevole ed efficiente sia:
inline bool operator==(const MyStruct1& lhs, const MyStruct1& rhs)
{
return lhs.my_struct2 == rhs.my_struct2 &&
lhs.an_int == rhs.an_int;
}
Nota che anche questo richiede un operator==
per MyStruct2
.
Le implicazioni di questa implementazione e le alternative sono discusse nella sezione Discussione delle specifiche di MyStruct1 di seguito.
Un approccio coerente a ==, <,> <= ecc
È facile sfruttare std::tuple
gli operatori di confronto di per confrontare le proprie istanze di classe: basta usarle std::tie
per creare tuple di riferimenti ai campi nell'ordine di confronto desiderato. Generalizzando il mio esempio da qui :
inline bool operator==(const MyStruct1& lhs, const MyStruct1& rhs)
{
return std::tie(lhs.my_struct2, lhs.an_int) ==
std::tie(rhs.my_struct2, rhs.an_int);
}
inline bool operator<(const MyStruct1& lhs, const MyStruct1& rhs)
{
return std::tie(lhs.my_struct2, lhs.an_int) <
std::tie(rhs.my_struct2, rhs.an_int);
}
// ...etc...
Quando "possiedi" (ovvero puoi modificare un fattore con librerie aziendali e di terze parti) la classe che desideri confrontare, e specialmente con la preparazione di C ++ 14 nel dedurre il tipo di ritorno della funzione dall'istruzione return
, è spesso più bello aggiungere un " lega "la funzione membro alla classe che vuoi poter confrontare:
auto tie() const { return std::tie(my_struct1, an_int); }
Quindi i confronti sopra si semplificano per:
inline bool operator==(const MyStruct1& lhs, const MyStruct1& rhs)
{
return lhs.tie() == rhs.tie();
}
Se desideri un set più completo di operatori di confronto, ti suggerisco di aumentare gli operatori (cerca less_than_comparable
). Se per qualche motivo non è adatto, l'idea delle macro di supporto (online) potrebbe piacerti o meno :
#define TIED_OP(STRUCT, OP, GET_FIELDS) \
inline bool operator OP(const STRUCT& lhs, const STRUCT& rhs) \
{ \
return std::tie(GET_FIELDS(lhs)) OP std::tie(GET_FIELDS(rhs)); \
}
#define TIED_COMPARISONS(STRUCT, GET_FIELDS) \
TIED_OP(STRUCT, ==, GET_FIELDS) \
TIED_OP(STRUCT, !=, GET_FIELDS) \
TIED_OP(STRUCT, <, GET_FIELDS) \
TIED_OP(STRUCT, <=, GET_FIELDS) \
TIED_OP(STRUCT, >=, GET_FIELDS) \
TIED_OP(STRUCT, >, GET_FIELDS)
... che può quindi essere utilizzato a la ...
#define MY_STRUCT_FIELDS(X) X.my_struct2, X.an_int
TIED_COMPARISONS(MyStruct1, MY_STRUCT_FIELDS)
(C ++ 14 versione con legami di membri qui )
Discussione delle specifiche del tuo MyStruct1
Ci sono delle implicazioni nella scelta di fornire un membro indipendente rispetto a un membro operator==()
...
Implementazione indipendente
Hai una decisione interessante da prendere. Poiché la tua classe può essere costruita implicitamente da a MyStruct2
, una funzione indipendente / non membro bool operator==(const MyStruct2& lhs, const MyStruct2& rhs)
supporterebbe ...
my_MyStruct2 == my_MyStruct1
... creando prima un temporaneo MyStruct1
da my_myStruct2
, poi facendo il confronto. Ciò lascerebbe sicuramente MyStruct1::an_int
impostato il valore del parametro predefinito del costruttore di -1
. A seconda se si include an_int
il confronto per l'attuazione della vostra operator==
, un MyStruct1
potrebbe o non potrebbe confrontare uguale ad una MyStruct2
che si confronta uguale al MyStruct1
's my_struct_2
membro! Inoltre, la creazione di un temporaneo MyStruct1
può essere un'operazione molto inefficiente, poiché implica la copia del my_struct2
membro esistente in un temporaneo, solo per gettarlo via dopo il confronto. (Ovviamente, potresti impedire questa costruzione implicita di MyStruct1
s per il confronto creando quel costruttore explicit
o rimuovendo il valore predefinito per an_int
.)
Implementazione dei membri
Se vuoi evitare la costruzione implicita di a MyStruct1
da a MyStruct2
, rendi l'operatore di confronto una funzione membro:
struct MyStruct1
{
...
bool operator==(const MyStruct1& rhs) const
{
return tie() == rhs.tie(); // or another approach as above
}
};
Si noti che la const
parola chiave, necessaria solo per l'implementazione dei membri, avvisa il compilatore che il confronto degli oggetti non li modifica, quindi può essere consentito sugli const
oggetti.
Confronto delle rappresentazioni visibili
A volte il modo più semplice per ottenere il tipo di confronto desiderato può essere ...
return lhs.to_string() == rhs.to_string();
... che spesso è anche molto costoso - quelli string
creati dolorosamente solo per essere gettati via! Per i tipi con valori in virgola mobile, confrontare le rappresentazioni visibili significa che il numero di cifre visualizzate determina la tolleranza entro la quale i valori quasi uguali vengono trattati come uguali durante il confronto.
struct
di uguaglianza? E se vuoi il modo semplice, c'è semprememcmp
così tanto tempo che le tue strutture non contengono puntatore.