Quiero comparar estructuras de forma genérica y he hecho algo como esto (no puedo compartir la fuente real, así que solicite más detalles si es necesario):
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;
}
Esto funciona principalmente según lo previsto, excepto que a veces devuelve falso a pesar de que las dos instancias de estructura tienen miembros idénticos (lo he comprobado con el depurador eclipse). Después de algunas búsquedas descubrí que memcmppuede fallar debido a que la estructura utilizada está rellenada.
¿Hay una forma más adecuada de comparar la memoria que es indiferente al relleno? No puedo modificar las estructuras utilizadas (son parte de una API que estoy usando) y las muchas estructuras diferentes utilizadas tienen algunos miembros diferentes y, por lo tanto, no se pueden comparar individualmente de forma genérica (que yo sepa).
Editar: desafortunadamente estoy atascado con C ++ 11. Debería haber mencionado esto antes ...

memcmpincluye esos bits de relleno en su comparación.==operador. El usomemcmpno es confiable, y tarde o temprano tendrás que lidiar con alguna clase que tiene que "hacerlo un poco diferente de las demás". Es muy limpio y eficiente implementar eso en un operador. El comportamiento real será polimórfico pero el código fuente estará limpio ... y, obviamente.Respuestas:
No,
memcmpno es adecuado para hacer esto. Y la reflexión en C ++ es insuficiente para hacer esto en este momento (habrá compiladores experimentales que admitirán la reflexión lo suficientemente fuerte como para hacerlo ya, y c ++ 23 podría tener las características que necesita).Sin una reflexión incorporada, la forma más fácil de resolver su problema es hacer una reflexión manual.
Toma esto:
queremos hacer la mínima cantidad de trabajo para poder comparar dos de estos.
Si tenemos:
o
para c ++ 11 , entonces:
hace un trabajo bastante decente.
Podemos extender este proceso para que sea recursivo con un poco de trabajo; en lugar de comparar vínculos, compare cada elemento envuelto en una plantilla, y esa plantilla
operator==aplica esta regla de manera recursiva (ajustando el elementoas_tiepara comparar) a menos que el elemento ya tenga un funcionamiento==y maneje matrices.Esto requerirá un poco de una biblioteca (¿100 líneas de código?) Junto con la escritura de un poco de datos de "reflexión" manuales por miembro. Si el número de estructuras que tiene es limitado, podría ser más fácil escribir el código por estructura manualmente.
Probablemente hay formas de obtener
para generar la
as_tieestructura usando macros horribles. Peroas_tiees lo suficientemente simple. En c ++ 11 la repetición es molesta; esto es útil:En esta situación y en muchas otras. Con
RETURNS, la escrituraas_tiees:eliminando la repetición.
Aquí hay una puñalada para hacerlo recursivo:
c ++ 17 refl_tie (array) (totalmente recursivo, incluso admite matrices de matrices):
Ejemplo en vivo .
Aquí uso un
std::arrayderefl_tie. Esto es mucho más rápido que mi tupla anterior de refl_tie en tiempo de compilación.también
usar
std::crefaquí en lugar destd::tiepodría ahorrar tiempo de compilación, ya quecrefes una clase mucho más simple quetuple.Finalmente, deberías agregar
lo que evitará que los miembros de la matriz se descompongan en punteros y vuelvan a caer en la igualdad de puntero (que probablemente no desee de las matrices).
Sin esto, si pasa una matriz a una estructura no reflejada, recurre a una estructura de puntero a no reflejada
refl_tie, que funciona y devuelve tonterías.Con esto, terminas con un error en tiempo de compilación.
El soporte para la recursividad a través de tipos de biblioteca es complicado. Podrías
std::tieellos:pero eso no admite la recursividad a través de él.
fuente
as_tie. A partir de C ++ 14, esto se deduce automáticamente. Puede usarauto as_tie (some_struct const & s) -> decltype(std::tie(s.x, s.d1, s.d2, s.c));en C ++ 11. O indique explícitamente el tipo de retorno.as_tiesoporte, funciona automáticamente) y los miembros de la matriz de soporte no están detallados, pero es posible.inlinepalabra clave debería hacer que desaparezcan los errores de definición múltiple. Use el botón [preguntar] después de obtener un ejemplo reproducible mínimoTiene razón en que el relleno interfiere en su forma de comparar tipos arbitrarios de esta manera.
Hay medidas que puede tomar:
Data, por ejemplo, gcc tiene__attribute__((packed)). Tiene impacto en el rendimiento, pero puede valer la pena intentarlo. Sin embargo, debo admitir que no sé si lepackedpermite no permitir el relleno por completo. Gcc doc dice:Data, al menosstd::has_unique_object_representations<T>puede decirle si su comparación arrojará resultados correctos:y además:
PD: solo abordé el relleno, pero no olvide que los tipos que se pueden comparar para instancias con representación diferente en la memoria no son raros (por ejemplo
std::string,std::vectory muchos otros).fuente
memcmpen estructuras sin relleno e implementaroperator==solo cuando sea necesario.En resumen: no es posible de forma genérica.
El problema
memcmpes que el relleno puede contener datos arbitrarios y, por lo tanto,memcmppuede fallar. Si hubiera una manera de averiguar dónde está el relleno, podría poner a cero esos bits y luego comparar las representaciones de datos, eso verificaría la igualdad si los miembros son trivialmente comparables (lo cual no es el caso, es decir,std::stringya que dos cadenas pueden contienen punteros diferentes, pero los dos conjuntos de caracteres puntiagudos son iguales). Pero no conozco ninguna forma de llegar al relleno de estructuras. Puede intentar decirle a su compilador que empaque las estructuras, pero esto hará que los accesos sean más lentos y realmente no está garantizado para funcionar.La forma más limpia de implementar esto es comparar a todos los miembros. Por supuesto, esto no es realmente posible de forma genérica (hasta que obtengamos reflexiones de tiempo de compilación y metaclases en C ++ 23 o posterior). Desde C ++ 20 en adelante, uno podría generar un valor predeterminado,
operator<=>pero creo que esto también solo sería posible como función miembro, por lo que, nuevamente, esto no es realmente aplicable. Si tiene suerte y todas las estructuras que desea comparar tienen unaoperator==definida, puede, por supuesto, usarla. Pero eso no está garantizado.EDITAR: Ok, en realidad hay una forma totalmente hacky y algo genérica para los agregados. (Solo escribí la conversión a tuplas, esas tienen un operador de comparación predeterminado). perno
fuente
C ++ 20 admite comaparisons predeterminados
fuente
==o<=>solo se pueden hacer en el alcance de la clase.Suponiendo datos POD, el operador de asignación predeterminado copia solo los bytes miembros. (en realidad no estoy 100% seguro de eso, no confíes en mi palabra)
Puedes usar esto a tu favor:
fuente
Creo que puede basar una solución en el vudú maravillosamente tortuoso de Antony Polukhin en la
magic_getbiblioteca, para estructuras, no para clases complejas.Con esa biblioteca, podemos iterar los diferentes campos de una estructura, con su tipo apropiado, en código de plantilla puramente general. Antony ha utilizado esto, por ejemplo, para poder transmitir estructuras arbitrarias a una secuencia de salida con los tipos correctos, de forma completamente genérica. Es lógico pensar que la comparación también podría ser una posible aplicación de este enfoque.
... pero necesitarías C ++ 14. Al menos es mejor que las sugerencias de C ++ 17 y posteriores en otras respuestas :-P
fuente