Ich möchte Strukturen generisch vergleichen und habe so etwas getan (ich kann die tatsächliche Quelle nicht teilen, bitte fragen Sie bei Bedarf nach weiteren Details):
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;
}
Dies funktioniert meistens wie beabsichtigt, außer dass manchmal false zurückgegeben wird, obwohl die beiden Strukturinstanzen identische Mitglieder haben (ich habe dies mit dem Eclipse-Debugger überprüft). Nach einiger Suche stellte ich fest, dass memcmp
dies aufgrund der aufgefüllten Struktur fehlschlagen kann.
Gibt es eine geeignetere Methode zum Vergleichen des Speichers, die dem Auffüllen gleichgültig ist? Ich kann die verwendeten Strukturen nicht ändern (sie sind Teil einer von mir verwendeten API) und die vielen verschiedenen verwendeten Strukturen haben einige unterschiedliche Mitglieder und können daher meines Wissens nicht generisch einzeln verglichen werden.
Edit: Ich bin leider mit C ++ 11 stecken. Hätte das früher erwähnen sollen ...
memcmp
diese Füllbits in Ihren Vergleich einbeziehen.==
Operator. Die Verwendungmemcmp
ist unzuverlässig, und früher oder später werden Sie es mit einer Klasse zu tun haben, die "es ein wenig anders machen muss als die anderen". Es ist sehr sauber und effizient, dies in einem Bediener zu implementieren. Das tatsächliche Verhalten ist polymorph, aber der Quellcode ist sauber ... und offensichtlich.Antworten:
Nein,
memcmp
ist dazu nicht geeignet. Und die Reflexion in C ++ reicht zu diesem Zeitpunkt nicht aus, um dies zu tun (es wird experimentelle Compiler geben, die die Reflexion unterstützen, die stark genug ist, um dies bereits zu tun, und c ++ 23 verfügt möglicherweise über die Funktionen, die Sie benötigen).Ohne eingebaute Reflexion können Sie Ihr Problem am einfachsten durch manuelle Reflexion lösen.
Nimm das:
Wir wollen den minimalen Arbeitsaufwand erledigen, damit wir zwei davon vergleichen können.
Wenn wir haben:
oder
für c ++ 11 dann:
macht einen ziemlich anständigen Job.
Wir können diesen Prozess mit ein wenig Arbeit so erweitern, dass er rekursiv ist. Anstatt Bindungen zu vergleichen, vergleichen Sie jedes Element, das in eine Vorlage eingeschlossen ist, und diese Vorlage
operator==
wendet diese Regel rekursiv an (das Elementas_tie
zum Vergleich einschließen), es sei denn, das Element verfügt bereits über eine Funktion==
und verarbeitet Arrays.Dies erfordert ein bisschen Bibliothek (100 Codezeilen?) Zusammen mit dem Schreiben von manuellen "Reflexions" -Daten pro Mitglied. Wenn die Anzahl Ihrer Strukturen begrenzt ist, ist es möglicherweise einfacher, Code pro Struktur manuell zu schreiben.
Es gibt wahrscheinlich Möglichkeiten zu bekommen
die
as_tie
Struktur mit schrecklichen Makros zu generieren . Istas_tie
aber einfach genug. In c ++ 11 ist die Wiederholung ärgerlich; das ist nützlich:in dieser Situation und vielen anderen. Mit
RETURNS
Schreibenas_tie
ist:Entfernen der Wiederholung.
Hier ist ein Versuch, es rekursiv zu machen:
c ++ 17 refl_tie (Array) (vollständig rekursiv, unterstützt sogar Arrays von Arrays):
Live Beispiel .
Hier benutze ich ein
std::array
vonrefl_tie
. Dies ist viel schneller als mein vorheriges Tupel von refl_tie zur Kompilierungszeit.Ebenfalls
Wenn Sie
std::cref
hier anstelle vonstd::tie
verwenden, können Sie Overhead beim Kompilieren sparen, da diescref
eine viel einfachere Klasse ist alstuple
.Schließlich sollten Sie hinzufügen
Dies verhindert, dass Array-Mitglieder in Zeiger zerfallen und auf die Zeigergleichheit zurückgreifen (was Sie von Arrays wahrscheinlich nicht wollen).
Ohne dies wird, wenn Sie ein Array an eine nicht reflektierte Struktur in übergeben, auf eine Zeiger-zu-nicht-reflektierte Struktur zurückgegriffen
refl_tie
, die funktioniert und Unsinn zurückgibt.Dies führt zu einem Fehler bei der Kompilierung.
Die Unterstützung der Rekursion durch Bibliothekstypen ist schwierig. Sie könnten
std::tie
sie:aber das unterstützt keine Rekursion.
quelle
as_tie
. Ab C ++ 14 wird dies automatisch abgeleitet. Sie könnenauto as_tie (some_struct const & s) -> decltype(std::tie(s.x, s.d1, s.d2, s.c));
in C ++ 11 verwenden. Oder geben Sie den Rückgabetyp explizit an.as_tie
Unterstützung haben, funktioniert automatisch) und die Unterstützung von Array-Mitgliedern, ist nicht detailliert, aber möglich.inline
Schlüsselwort sollte dazu führen, dass mehrere Definitionsfehler verschwinden . Verwenden Sie die Schaltfläche [Frage stellen], nachdem Sie ein minimal reproduzierbares Beispiel erhalten habenSie haben Recht, dass das Auffüllen den Vergleich beliebiger Typen auf diese Weise behindert.
Es gibt Maßnahmen, die Sie ergreifen können:
Data
dann hat zB gcc__attribute__((packed))
. Es hat Auswirkungen auf die Leistung, aber es könnte sich lohnen, es auszuprobieren. Allerdings muss ich zugeben, dass ich nicht weiß, obpacked
Sie die Polsterung vollständig verbieten können. Gcc doc sagt:Data
zumindeststd::has_unique_object_representations<T>
feststellen, ob Ihr Vergleich zu korrekten Ergebnissen führt:und weiter:
PS: Ich habe nur adressierte Polsterung, aber nicht , dass Typen vergessen , das für Instanzen mit unterschiedlicher Darstellung im Speicher ist keineswegs selten (zB gleich vergleichen
std::string
,std::vector
und viele andere).quelle
memcmp
Strukturen ohne Auffüllung zu verwenden undoperator==
nur bei Bedarf zu implementieren .Kurzum: Generisch nicht möglich.
Das Problem dabei
memcmp
ist, dass das Auffüllen beliebige Daten enthalten kann und dahermemcmp
möglicherweise fehlschlägt. Wenn es eine Möglichkeit gäbe, herauszufinden, wo sich die Auffüllung befindet, könnten Sie diese Bits auf Null setzen und dann die Datendarstellungen vergleichen, um die Gleichheit zu überprüfen, wenn die Elemente trivial vergleichbar sind (was nicht der Fall ist, dhstd::string
da zwei Zeichenfolgen dies können enthalten unterschiedliche Zeiger, aber die beiden spitzen Zeichen-Arrays sind gleich). Aber ich kenne keine Möglichkeit, an das Auffüllen von Strukturen heranzukommen. Sie können versuchen, Ihren Compiler anzuweisen, die Strukturen zu packen. Dies verlangsamt jedoch den Zugriff und ist nicht wirklich garantiert.Der sauberste Weg, dies zu implementieren, besteht darin, alle Mitglieder zu vergleichen. Natürlich ist dies nicht generisch möglich (bis wir Kompilierungszeitreflexionen und Metaklassen in C ++ 23 oder höher erhalten). Ab C ++ 20 könnte man einen Standard generieren,
operator<=>
aber ich denke, dies wäre auch nur als Mitgliedsfunktion möglich, so dass dies wiederum nicht wirklich anwendbar ist. Wenn Sie Glück haben und alle Strukturen, die Sie vergleichen möchten, eineoperator==
definierte haben, können Sie diese natürlich einfach verwenden. Das ist aber nicht garantiert.EDIT: Ok, es gibt tatsächlich einen total hackigen und etwas generischen Weg für Aggregate. (Ich habe nur die Konvertierung in Tupel geschrieben, diese haben einen Standardvergleichsoperator). Godbolt
quelle
C ++ 20 unterstützt Standardvergleiche
quelle
==
oder<=>
nur ausgeführt werden können im Klassenumfang.Unter der Annahme von POD-Daten kopiert der Standardzuweisungsoperator nur Mitgliedsbytes. (Eigentlich nicht 100% sicher, nimm mein Wort nicht dafür)
Sie können dies zu Ihrem Vorteil nutzen:
quelle
Ich glaube, Sie können möglicherweise eine Lösung auf Antony Polukhins wunderbar verschlagenem Voodoo in der
magic_get
Bibliothek finden - für Strukturen, nicht für komplexe Klassen.Mit dieser Bibliothek können wir die verschiedenen Felder einer Struktur mit ihrem entsprechenden Typ in rein allgemeinem Vorlagencode iterieren. Antony hat dies beispielsweise verwendet, um beliebige Strukturen vollständig generisch in einen Ausgabestream mit den richtigen Typen streamen zu können. Es liegt auf der Hand, dass ein Vergleich auch eine mögliche Anwendung dieses Ansatzes sein könnte.
... aber du brauchst C ++ 14. Zumindest ist es besser als das C ++ 17 und spätere Vorschläge in anderen Antworten :-P
quelle