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 memcmp
puede 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 ...
memcmp
incluye esos bits de relleno en su comparación.==
operador. El usomemcmp
no 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,
memcmp
no 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_tie
para 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_tie
estructura usando macros horribles. Peroas_tie
es 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_tie
es: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::array
derefl_tie
. Esto es mucho más rápido que mi tupla anterior de refl_tie en tiempo de compilación.también
usar
std::cref
aquí en lugar destd::tie
podría ahorrar tiempo de compilación, ya quecref
es 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::tie
ellos: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_tie
soporte, funciona automáticamente) y los miembros de la matriz de soporte no están detallados, pero es posible.inline
palabra 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 lepacked
permite 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::vector
y muchos otros).fuente
memcmp
en estructuras sin relleno e implementaroperator==
solo cuando sea necesario.En resumen: no es posible de forma genérica.
El problema
memcmp
es que el relleno puede contener datos arbitrarios y, por lo tanto,memcmp
puede 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::string
ya 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_get
biblioteca, 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