Chcę porównać struktury w ogólny sposób i zrobiłem coś takiego (nie mogę udostępnić faktycznego źródła, więc w razie potrzeby poproś o więcej szczegółów):
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;
}
Działa to głównie zgodnie z przeznaczeniem, z tym że czasami zwraca false, mimo że dwie instancje struct mają identyczne elementy (sprawdziłem za pomocą debugera Eclipse). Po kilku poszukiwaniach odkryłem, że memcmp
może się nie udać z powodu wypełnienia użytej struktury.
Czy istnieje bardziej odpowiedni sposób porównywania pamięci, która jest obojętna na wypełnianie? Nie jestem w stanie modyfikować używanych struktur (są one częścią interfejsu API, którego używam), a wiele różnych używanych struktur ma różne elementy i dlatego nie można ich porównywać indywidualnie w sposób ogólny (o ile mi wiadomo).
Edycja: Niestety niestety utknąłem w C ++ 11. Powinienem był o tym wspomnieć wcześniej ...
memcmp
uwzględnieniu tych bitów wypełniających w porównaniu.==
operator. Używaniememcmp
jest niewiarygodne, a wcześniej czy później będziesz miał do czynienia z pewną klasą, która musi „zrobić to trochę inaczej niż inne”. Zaimplementowanie tego u operatora jest bardzo czyste i wydajne. Rzeczywiste zachowanie będzie polimorficzne, ale kod źródłowy będzie czysty ... i oczywiście.Odpowiedzi:
Nie,
memcmp
nie nadaje się do tego. Odbicie w C ++ jest w tym momencie niewystarczające (będą kompilatory eksperymentalne, które obsługują odbicie wystarczająco silne, aby to zrobić już, i c ++ 23 może mieć potrzebne funkcje).Bez wbudowanego odbicia najłatwiejszym sposobem rozwiązania problemu jest ręczne odbicie.
Weź to:
chcemy wykonać minimalną ilość pracy, abyśmy mogli porównać dwa z nich.
Jeśli mamy:
lub
dla c ++ 11 , a następnie:
wykonuje całkiem przyzwoitą robotę.
Możemy rozszerzyć ten proces na rekurencyjny przy odrobinie pracy; zamiast porównywać więzi, porównaj każdy element zawinięty w szablon, a szablon
operator==
rekurencyjnie stosuje tę regułę (zawijanie elementu was_tie
celu porównania), chyba że element ma już działający==
, i nie obsługuje tablic.Będzie to wymagało odrobiny biblioteki (100 linii wiersza kodu?) Wraz z zapisaniem odrobiny ręcznych danych „refleksyjnych” dla poszczególnych członków. Jeśli liczba posiadanych struktur jest ograniczona, łatwiejsze może być ręczne napisanie kodu dla struktury.
Prawdopodobnie istnieją sposoby na zdobycie
do generowania
as_tie
struktury przy użyciu okropnych makr. Aleas_tie
to jest dość proste. W c ++ 11 powtórzenie jest denerwujące; jest to przydatne:w tej sytuacji i wielu innych. Dzięki
RETURNS
, pisanieas_tie
jest:usunięcie powtórzenia.
Oto próba uczynienia go rekurencyjnym:
c ++ 17 refl_tie (tablica) (w pełni rekurencyjny, obsługuje nawet tablice tablic):
Przykład na żywo .
Tutaj używam
std::array
odrefl_tie
. Jest to znacznie szybsze niż moja poprzednia krotka refl_tie w czasie kompilacji.Również
użycie
std::cref
tutaj zamiaststd::tie
może zaoszczędzić na nakładach czasu kompilacji, ponieważcref
jest to znacznie prostsza klasa niżtuple
.Na koniec powinieneś dodać
co zapobiegnie rozkładaniu się elementów tablicy na wskaźniki i cofaniu się do równości wskaźnika (czego prawdopodobnie nie chcesz od tablic).
Bez tego, jeśli przekażesz tablicę do struktury nie odzwierciedlonej, wraca ona do struktury wskaźnikowej do nie odzwierciedlonej
refl_tie
, która działa i zwraca bzdury.Z tego wynika błąd czasu kompilacji.
Obsługa rekurencji za pomocą typów bibliotek jest trudna. Mógłbyś
std::tie
im:ale to nie obsługuje rekurencji.
źródło
as_tie
. Począwszy od C ++ 14 jest to wydedukowane automatycznie. Możesz używaćauto as_tie (some_struct const & s) -> decltype(std::tie(s.x, s.d1, s.d2, s.c));
w C ++ 11. Lub wyraźnie podaj typ zwrotu.as_tie
wsparcie, działa automatycznie) i członkowie tablicy wsparcia nie są szczegółowe, ale jest to możliwe.inline
kluczowe powinno spowodować zniknięcie wielu błędów definicji. Użyj przycisku [zadaj pytanie] po otrzymaniu minimalnego odtwarzalnego przykładuMasz rację, że wypełnienie przeszkadza ci w porównywaniu dowolnych typów w ten sposób.
Istnieją środki, które możesz podjąć:
Data
np. Gcc ma__attribute__((packed))
. Wpływa to na wydajność, ale warto spróbować. Chociaż muszę przyznać, że nie wiem, czypacked
pozwala ci całkowicie zabronić paddingowania. Dokument Gcc mówi:Data
przynajmniejstd::has_unique_object_representations<T>
powiedzieć, czy Twoje porównanie da prawidłowe wyniki:i dalej:
PS: Ja tylko skierowana wyściółkę, ale nie zapomnij, że typy, które można porównać równe dla przypadków z różnych reprezentacji w pamięci nie są bynajmniej rzadkich (np
std::string
,std::vector
i wiele innych).źródło
memcmp
na strukturach bez wypełniania i implementowaćoperator==
tylko w razie potrzeby.Krótko mówiąc: niemożliwe w sposób ogólny.
Problem z
memcmp
polega na tym, że wypełnienie może zawierać dowolne dane, a zatemmemcmp
może się nie powieść. Gdyby istniał sposób, aby dowiedzieć się, gdzie jest wypełnienie, można wyzerować te bity, a następnie porównać reprezentacje danych, co sprawdziłoby równość, jeśli elementy są trywialnie porównywalne (co nie jest prawdą,std::string
ponieważ dwa ciągi znaków mogą zawierają różne wskaźniki, ale dwa spiczaste tablice znaków są równe). Ale nie znam sposobu, by dostać się do wypełnienia struktur. Możesz spróbować powiedzieć swojemu kompilatorowi, aby spakował struktury, ale spowoduje to spowolnienie dostępu i nie będzie tak naprawdę działać.Najczystszym sposobem na wdrożenie tego jest porównanie wszystkich członków. Oczywiście nie jest to tak naprawdę możliwe w ogólny sposób (dopóki nie otrzymamy odbić czasowych i meta klas w C ++ 23 lub nowszych). Począwszy od C ++ 20 można generować wartość domyślną,
operator<=>
ale myślę, że byłoby to również możliwe tylko jako funkcja składowa, więc znowu nie ma to zastosowania. Jeśli masz szczęście i wszystkie struktury, które chcesz porównać, mająoperator==
zdefiniowane, możesz oczywiście tego po prostu użyć. Ale to nie jest gwarantowane.EDYCJA: Ok, istnieje naprawdę zhackowany i nieco ogólny sposób na agregacje. (Napisałem konwersję tylko do krotek, te mają domyślny operator porównania). godbolt
źródło
C ++ 20 obsługuje domyślne kombinacje
źródło
==
lub<=>
można wykonać tylko w zakresie zajęć.Zakładając dane POD, domyślny operator przypisania kopiuje tylko bajty członka. (właściwie nie jestem w 100% tego pewien, nie wierz mi na słowo)
Możesz to wykorzystać na swoją korzyść:
źródło
Myślę, że możesz oprzeć rozwiązanie na cudownie przebiegłym voodoo Antony'ego Polukhina w
magic_get
bibliotece - dla struktur, a nie dla złożonych klas.Dzięki tej bibliotece jesteśmy w stanie iterować różne pola struktury, z ich odpowiednim typem, w kodzie o czysto ogólnym szablonie. Antony wykorzystał to na przykład, aby móc przesyłać dowolne struktury do strumienia wyjściowego o prawidłowych typach, całkowicie ogólnie. Jest oczywiste, że porównanie może być również możliwym zastosowaniem tego podejścia.
... ale potrzebujesz C ++ 14. Przynajmniej jest lepszy niż C ++ 17 i późniejsze sugestie w innych odpowiedziach :-P
źródło