Quero comparar estruturas de uma maneira genérica e fiz algo parecido com isto (não posso compartilhar a fonte real, portanto, solicite mais detalhes, se necessário):
template<typename Data>
bool structCmp(Data data1, Data data2)
{
void* dataStart1 = (std::uint8_t*)&data1;
void* dataStart2 = (std::uint8_t*)&data2;
return memcmp(dataStart1, dataStart2, sizeof(Data)) == 0;
}
Isso funciona principalmente como planejado, exceto que, às vezes, retorna false, mesmo que as duas instâncias struct tenham membros idênticos (verifiquei com o depurador do eclipse). Depois de algumas pesquisas, descobri que memcmp
pode falhar devido ao uso da estrutura usada.
Existe uma maneira mais adequada de comparar a memória indiferente ao preenchimento? Não consigo modificar as estruturas usadas (elas fazem parte de uma API que estou usando) e as muitas estruturas diferentes usadas têm alguns membros diferentes e, portanto, não podem ser comparadas individualmente de maneira genérica (pelo que sei).
Edit: infelizmente estou preso com C ++ 11. Deveria ter mencionado isso antes ...
memcmp
inclui esses bits de preenchimento em sua comparação.==
operador. O usomemcmp
não é confiável e, mais cedo ou mais tarde, você estará lidando com alguma classe que precisa "fazer um pouco diferente das outras". É muito limpo e eficiente implementar isso em um operador. O comportamento real será polimórfico, mas o código fonte será limpo ... e, óbvio.Respostas:
Não,
memcmp
não é adequado para fazer isso. E a reflexão no C ++ é insuficiente para fazer isso neste momento (haverá compiladores experimentais que suportam a reflexão suficientemente forte para fazer isso, e o c ++ 23 pode ter os recursos necessários).Sem reflexão interna, a maneira mais fácil de resolver seu problema é fazer uma reflexão manual.
Pegue isso:
queremos fazer a quantidade mínima de trabalho para comparar dois deles.
Se tiver-mos:
ou
para c ++ 11 , então:
faz um trabalho bastante decente.
Podemos estender esse processo para ser recursivo com um pouco de trabalho; em vez de comparar laços, compare cada elemento envolvido em um modelo e o modelo
operator==
aplica recursivamente essa regra (envolvendo o elementoas_tie
para comparação), a menos que o elemento já tenha um funcionamento==
e lida com matrizes.Isso exigirá um pouco de uma biblioteca (100 linhas de código?), Juntamente com a gravação de um pouco de dados manuais de "reflexão" por membro. Se o número de estruturas que você possui é limitado, pode ser mais fácil escrever o código por estrutura manualmente.
Provavelmente existem maneiras de obter
para gerar a
as_tie
estrutura usando macros horríveis. Masas_tie
é bastante simples. Em c ++ 11, a repetição é irritante; isso é útil:nesta situação e em muitas outras. Com
RETURNS
, escreveras_tie
é:removendo a repetição.
Aqui está uma facada em torná-lo recursivo:
c ++ 17 refl_tie (array) (totalmente recursivo, até suporta matrizes de matrizes):
Exemplo ao vivo .
Aqui eu uso um
std::array
dosrefl_tie
. Isso é muito mais rápido que minha tupla anterior de refl_tie em tempo de compilação.Além disso
usar
std::cref
aqui em vez destd::tie
poderia economizar em custos adicionais em tempo de compilação, comocref
é uma classe muito mais simples quetuple
.Por fim, você deve adicionar
o que impedirá que os membros da matriz se deteriorem para ponteiros e retornem à igualdade de ponteiros (o que você provavelmente não deseja das matrizes).
Sem isso, se você passar uma matriz para uma estrutura não refletida, ela recai na estrutura ponteiro para não refletida
refl_tie
, que funciona e retorna um disparate.Com isso, você acaba com um erro em tempo de compilação.
O suporte à recursão através dos tipos de biblioteca é complicado. Você poderia
std::tie
:mas isso não suporta recursão por meio dele.
fonte
as_tie
. A partir do C ++ 14, isso é deduzido automaticamente. Você pode usarauto as_tie (some_struct const & s) -> decltype(std::tie(s.x, s.d1, s.d2, s.c));
no C ++ 11. Ou indique explicitamente o tipo de retorno.as_tie
suporte, funciona automaticamente) e os membros da matriz de suporte não são detalhados, mas é possível.inline
palavra-chave deve fazer com que vários erros de definição desapareçam. Use o botão [fazer pergunta] depois de obter um exemplo reproduzível mínimoVocê está certo de que o preenchimento interfere na comparação de tipos arbitrários dessa maneira.
Existem medidas que você pode tomar:
Data
, por exemplo, o gcc possui__attribute__((packed))
. Isso afeta o desempenho, mas pode valer a pena tentar. No entanto, devo admitir que não sei sepacked
permite que você desaprove completamente o preenchimento. O documento do Gcc diz:Data
, pelo menosstd::has_unique_object_representations<T>
pode dizer se sua comparação produzirá resultados corretos:e ainda mais:
PS: Abordei apenas o preenchimento, mas não esqueço que tipos que podem ser comparados em instâncias com diferentes representações na memória não são raros (por exemplo
std::string
,std::vector
e muitos outros).fonte
memcmp
em estruturas sem preenchimento e implementaroperator==
apenas quando necessário.Em resumo: não é possível de maneira genérica.
O problema
memcmp
é que o preenchimento pode conter dados arbitrários e, portanto,memcmp
pode falhar. Se houvesse uma maneira de descobrir onde está o preenchimento, você poderia zerar esses bits e comparar as representações de dados, que verificariam a igualdade se os membros fossem trivialmente comparáveis (o que não é o caso, ou seja,std::string
já que duas strings podem contêm ponteiros diferentes, mas as duas matrizes apontadas são iguais). Mas não sei como chegar ao preenchimento de estruturas. Você pode tentar dizer ao seu compilador para compactar as estruturas, mas isso tornará os acessos mais lentos e não é realmente garantido que funcione.A maneira mais limpa de implementar isso é comparar todos os membros. Obviamente, isso não é realmente possível de uma maneira genérica (até obtermos reflexões de tempo de compilação e meta classes no C ++ 23 ou posterior). A partir do C ++ 20 em diante, pode-se gerar um padrão,
operator<=>
mas acho que isso também só seria possível como uma função membro, portanto, novamente isso não é realmente aplicável. Se você tiver sorte e todas as estruturas que deseja comparar tiverem umaoperator==
definição, é claro que você pode apenas usar isso. Mas isso não é garantido.EDIT: Ok, existe realmente uma maneira totalmente hacky e um tanto genérica para agregados. (Eu escrevi apenas a conversão para tuplas, elas têm um operador de comparação padrão). godbolt
fonte
C ++ 20 suporta comaparisons padrão
fonte
==
ou<=>
só pode ser feita no escopo da classe.Supondo dados POD, o operador de atribuição padrão copia apenas bytes de membro. (na verdade, não tenho 100% de certeza disso, não aceite minha palavra)
Você pode utilizar isto para o seu benefício:
fonte
Acredito que você possa basear uma solução no vodu maravilhosamente desonesto de Antony Polukhin na
magic_get
biblioteca - para estruturas, não para classes complexas.Com essa biblioteca, podemos iterar os diferentes campos de uma estrutura, com seu tipo apropriado, no código de modelo puramente geral. Antony usou isso, por exemplo, para poder transmitir streams arbitrários para um stream de saída com os tipos corretos, de maneira completamente genérica. É lógico que a comparação também possa ser uma aplicação possível dessa abordagem.
... mas você precisaria de C ++ 14. Pelo menos é melhor que o C ++ 17 e sugestões posteriores em outras respostas :-P
fonte