C ++ 20 introduziu comparações padrão, também conhecidas como "nave espacial"operator<=>
, que permite que você solicite <
/ <=
/ ==
/ !=
/ >=
/ e / ou >
operadores gerados pelo compilador com a implementação óbvia / ingênua (?) ...
auto operator<=>(const MyClass&) const = default;
... mas você pode personalizar isso para situações mais complicadas (discutido abaixo). Veja aqui a proposta de idioma, que contém justificativas e discussão. Esta resposta continua relevante para C ++ 17 e anteriores e para uma visão de quando você deve personalizar a implementação de operator<=>
....
Pode parecer um pouco inútil para C ++ não ter padronizado isso anteriormente, mas muitas vezes structs / classes têm alguns membros de dados para excluir da comparação (por exemplo, contadores, resultados em cache, capacidade do contêiner, código de sucesso / erro da última operação, cursores), como bem como decisões a tomar sobre uma miríade de coisas, incluindo, mas não se limitando a:
- quais campos comparar primeiro, por exemplo, comparar um
int
membro específico pode eliminar 99% dos objetos desiguais muito rapidamente, enquanto um map<string,string>
membro pode muitas vezes ter entradas idênticas e ser relativamente caro para comparar - se os valores são carregados em tempo de execução, o programador pode ter insights sobre o compilador não pode
- na comparação de strings: distinção entre maiúsculas e minúsculas, equivalência de espaços em branco e separadores, convenções de escape ...
- precisão ao comparar flutuadores / duplos
- se os valores de ponto flutuante NaN devem ser considerados iguais
- comparar ponteiros ou dados apontados (e se for o último, como saber se os ponteiros são para matrizes e de quantos objetos / bytes precisam de comparação)
- se a ordem é importante ao comparar recipientes não classificados (por exemplo
vector
, list
) e, em caso afirmativo, se não há problema em classificá-los no local antes de comparar vs. usar memória extra para classificar temporários cada vez que uma comparação é feita
- quantos elementos da matriz atualmente contêm valores válidos que devem ser comparados (há um tamanho em algum lugar ou uma sentinela?)
- qual membro de
union
a comparar
- normalização: por exemplo, tipos de data podem permitir dia do mês ou mês do ano fora do intervalo, ou um objeto racional / fração pode ter 6/8 enquanto outro tem 3/4, que por motivos de desempenho eles corrigem preguiçosamente com uma etapa de normalização separada; você pode ter que decidir se deve acionar uma normalização antes da comparação
- o que fazer quando ponteiros fracos não são válidos
- como lidar com membros e bases que não
operator==
se implementam (mas podem ter compare()
ou operator<
ou str()
ou getters ...)
- quais bloqueios devem ser feitos durante a leitura / comparação de dados que outras threads podem querer atualizar
Portanto, é bom ter um erro até que você tenha pensado explicitamente sobre o que a comparação deve significar para sua estrutura específica, em vez de deixá-la compilar, mas não fornecer um resultado significativo em tempo de execução .
Dito isso, seria bom se C ++ lhe permitisse dizer bool operator==() const = default;
quando você decidiu que um ==
teste membro por membro "ingênuo" estava ok. O mesmo para !=
. Dado vários membros / bases, "default" <
, <=
, >
, e >=
implementações parecer impossível embora - em cascata com base na ordem de do possível, mas muito improvável que seja o que queria, dada conflitantes imperativos de ordenamento membro (bases de ser, necessariamente, antes de os membros, agrupando por declaração acessibilidade, construção / destruição antes do uso dependente). Para ser mais amplamente útil, C ++ precisaria de um novo membro de dados / sistema de anotação de base para orientar as escolhas - isso seria ótimo ter no Padrão, no entanto, idealmente acoplado com geração de código definido pelo usuário baseado em AST ... Espero isto'
Implementação típica de operadores de igualdade
Uma implementação plausível
É provável que uma implementação razoável e eficiente seja:
inline bool operator==(const MyStruct1& lhs, const MyStruct1& rhs)
{
return lhs.my_struct2 == rhs.my_struct2 &&
lhs.an_int == rhs.an_int;
}
Observe que isso também precisa de um operator==
para MyStruct2
.
As implicações desta implementação, e alternativas, são discutidas sob o título Discussão das especificações de seu MyStruct1 abaixo.
Uma abordagem consistente para ==, <,> <= etc
É fácil alavancar std::tuple
os operadores de comparação para comparar suas próprias instâncias de classe - use apenas std::tie
para criar tuplas de referências a campos na ordem de comparação desejada. Generalizando meu exemplo a partir daqui :
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 você "possui" (ou seja, pode editar, um fator com bibliotecas corporativas e de terceiros) a classe que deseja comparar, e especialmente com a preparação do C ++ 14 para deduzir o tipo de retorno de função da return
instrução, geralmente é melhor adicionar um " vincule a "função de membro à classe que você deseja comparar:
auto tie() const { return std::tie(my_struct1, an_int); }
Então, as comparações acima simplificam para:
inline bool operator==(const MyStruct1& lhs, const MyStruct1& rhs)
{
return lhs.tie() == rhs.tie();
}
Se você quiser um conjunto mais completo de operadores de comparação, sugiro operadores boost (pesquisar por less_than_comparable
). Se não for adequado por algum motivo, você pode ou não gostar da ideia de macros de suporte (online) :
#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)
... que pode então ser usado a la ...
#define MY_STRUCT_FIELDS(X) X.my_struct2, X.an_int
TIED_COMPARISONS(MyStruct1, MY_STRUCT_FIELDS)
(Versão C ++ 14 vinculado a membros aqui )
Discussão dos detalhes de sua MyStruct1
Existem implicações na escolha de fornecer um membro independente versus um membro operator==()
...
Implementação autônoma
Você tem uma decisão interessante a tomar. Como sua classe pode ser construída implicitamente a partir de um MyStruct2
, uma função independente / não membro bool operator==(const MyStruct2& lhs, const MyStruct2& rhs)
suportaria ...
my_MyStruct2 == my_MyStruct1
... criando primeiro um de temporário MyStruct1
e my_myStruct2
, em seguida, fazendo a comparação. Isso definitivamente deixaria MyStruct1::an_int
definido para o valor do parâmetro padrão do construtor de -1
. Dependendo se você incluir an_int
comparação na implementação do seu operator==
, um MyStruct1
pode ou não comparar igual a uma MyStruct2
que se compara igual ao MyStruct1
do my_struct_2
membro! Além disso, criar um temporário MyStruct1
pode ser uma operação muito ineficiente, pois envolve a cópia do my_struct2
membro existente para um temporário, apenas para descartá-lo após a comparação. (Claro, você poderia evitar esta construção implícita de MyStruct1
s para comparação, tornando esse construtor explicit
ou removendo o valor padrão para an_int
.)
Implementação de membro
Se você quiser evitar a construção implícita de a MyStruct1
de a MyStruct2
, torne o operador de comparação uma função-membro:
struct MyStruct1
{
...
bool operator==(const MyStruct1& rhs) const
{
return tie() == rhs.tie(); // or another approach as above
}
};
Observe a const
palavra-chave - necessária apenas para a implementação do membro - avisa o compilador que a comparação de objetos não os modifica, portanto, pode ser permitida em const
objetos.
Comparando as representações visíveis
Às vezes, a maneira mais fácil de obter o tipo de comparação que deseja pode ser ...
return lhs.to_string() == rhs.to_string();
... o que geralmente é muito caro também - aqueles string
são criados dolorosamente para serem jogados fora! Para tipos com valores de ponto flutuante, comparar representações visíveis significa que o número de dígitos exibidos determina a tolerância dentro da qual valores quase iguais são tratados como iguais durante a comparação.
struct
s por igualdade? E se você quiser a maneira mais simples, semprememcmp
há tanto tempo que suas estruturas não contêm ponteiro.