일반적인 방식으로 구조체를 비교하고 싶습니다. 이와 같은 작업을 수행했습니다 (실제 소스를 공유 할 수 없으므로 필요한 경우 자세한 내용을 요청하십시오).
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;
}
이것은 두 구조체 인스턴스가 동일한 멤버를 가지고 있어도 때때로 false를 반환한다는 점을 제외하고는 대부분 의도 한대로 작동합니다 (이클립스 디버거로 확인했습니다). 일부 검색 후 memcmp
사용 된 구조체가 채워 져서 실패 할 수 있음을 발견했습니다 .
패딩과 무관 한 메모리를 비교하는 더 적절한 방법이 있습니까? 사용 된 구조체 (사용중인 API의 일부)를 수정할 수 없으며 사용되는 많은 구조체에는 다른 멤버가 있으므로 일반적인 방식으로 (내 지식에 따라) 개별적으로 비교할 수 없습니다.
편집 : 불행히도 C ++ 11에 붙어 있습니다. 앞에서 언급 했어야했는데 …
답변
아니요, memcmp
이 작업에는 적합하지 않습니다. 그리고 C ++의 리플렉션은이 시점 에서이 작업을 수행하기에 충분하지 않습니다 (이미 수행 할 수있을만큼 강한 리플렉션을 지원하는 실험용 컴파일러가 있으며 c ++ 23 에는 필요한 기능이있을 수 있음).
내장 리플렉션이 없으면 문제를 해결하는 가장 쉬운 방법은 수동 리플렉션을 수행하는 것입니다.
이것을 가지고 :
struct some_struct {
int x;
double d1, d2;
char c;
};
우리는 최소한의 작업을 수행하여 두 가지를 비교할 수 있습니다.
우리가 가지고 있다면 :
auto as_tie(some_struct const& s){
return std::tie( s.x, s.d1, s.d2, s.c );
}
또는
auto as_tie(some_struct const& s)
-> decltype(std::tie( s.x, s.d1, s.d2, s.c ))
{
return std::tie( s.x, s.d1, s.d2, s.c );
}
를위한 C ++ 11 , 후 :
template<class S>
bool are_equal( S const& lhs, S const& rhs ) {
return as_tie(lhs) == as_tie(rhs);
}
꽤 괜찮은 일을합니다.
약간의 작업으로이 프로세스를 재귀 적으로 확장 할 수 있습니다. 동점을 비교하는 대신 템플릿에 래핑 된 각 요소 를 비교하면 요소에 이미 작업 이없고 배열을 처리 하지 않는 한 해당 템플릿 operator==
이이 규칙을 반복적으로 적용 as_tie
하여 비교할 요소를 래핑합니다 ==
.
이를 위해서는 약간의 라이브러리 (100 줄의 코드?)와 함께 멤버 별 “반사”데이터를 작성해야합니다. 가지고있는 구조체의 수가 제한되어 있다면, 구조체 당 코드를 수동으로 작성하는 것이 더 쉬울 수 있습니다.
얻을 수있는 방법이있을 수 있습니다
REFLECT( some_struct, x, d1, d2, c )
as_tie
끔찍한 매크로를 사용 하여 구조 를 생성 합니다. 그러나 as_tie
간단합니다. 에서 C ++ (11) 반복은 성가신; 이것은 유용합니다 :
#define RETURNS(...) \
noexcept(noexcept(__VA_ARGS__)) \
-> decltype(__VA_ARGS__) \
{ return __VA_ARGS__; }
이 상황과 다른 많은 것들에서. 와 함께 RETURNS
, 쓰기as_tie
것은 다음 같습니다.
auto as_tie(some_struct const& s)
RETURNS( std::tie( s.x, s.d1, s.d2, s.c ) )
반복 제거.
다음은 재귀 적 인 것입니다.
template<class T,
typename std::enable_if< !std::is_class<T>{}, bool>::type = true
>
auto refl_tie( T const& t )
RETURNS(std::tie(t))
template<class...Ts,
typename std::enable_if< (sizeof...(Ts) > 1), bool>::type = true
>
auto refl_tie( Ts const&... ts )
RETURNS(std::make_tuple(refl_tie(ts)...))
template<class T, std::size_t N>
auto refl_tie( T const(&t)[N] ) {
// lots of work in C++11 to support this case, todo.
// in C++17 I could just make a tie of each of the N elements of the array?
// in C++11 I might write a custom struct that supports an array
// reference/pointer of fixed size and implements =, ==, !=, <, etc.
}
struct foo {
int x;
};
struct bar {
foo f1, f2;
};
auto refl_tie( foo const& s )
RETURNS( refl_tie( s.x ) )
auto refl_tie( bar const& s )
RETURNS( refl_tie( s.f1, s.f2 ) )
c ++ 17 refl_tie (array) (완전히 재귀 적이며 배열 배열을 지원합니다) :
template<class T, std::size_t N, std::size_t...Is>
auto array_refl( T const(&t)[N], std::index_sequence<Is...> )
RETURNS( std::array<decltype( refl_tie(t[0]) ), N>{ refl_tie( t[Is] )... } )
template<class T, std::size_t N>
auto refl_tie( T(&t)[N] )
RETURNS( array_refl( t, std::make_index_sequence<N>{} ) )
라이브 예 .
여기에 나는 std::array
의 refl_tie
. 이것은 컴파일 타임에 이전의 refl_tie 튜플보다 훨씬 빠릅니다.
또한
template<class T,
typename std::enable_if< !std::is_class<T>{}, bool>::type = true
>
auto refl_tie( T const& t )
RETURNS(std::cref(t))
std::cref
대신 여기를 사용 std::tie
하면 컴파일 타임 오버 헤드를 줄일 수 있습니다.cref
하면보다 간단한 클래스tuple
있습니다.
마지막으로
template<class T, std::size_t N, class...Ts>
auto refl_tie( T(&t)[N], Ts&&... ) = delete;
이것은 배열 구성원이 포인터로 붕괴하고 포인터 평등 (어쩌면 배열에서 원하지 않는)으로 되돌아 가지 못하게합니다.
이것이 없으면 배열을 반사되지 않은 구조체에 전달하면 포인터가 반사되지 않은 구조체로 돌아갑니다. refl_tie
작동하고 넌센스를 반환합니다.
이로 인해 컴파일 타임 오류가 발생합니다.
라이브러리 유형을 통한 재귀 지원은 까다 롭습니다. 당신은 std::tie
그들에게 할 수 있습니다 :
template<class T, class A>
auto refl_tie( std::vector<T, A> const& v )
RETURNS( std::tie(v) )
그러나 그것은 그것을 통한 재귀를 지원하지 않습니다.
답변
패딩이 이런 식으로 임의의 유형을 비교하는 방식에 맞는 것이 맞습니다.
취할 수있는 조치가 있습니다 :
- 당신이 통제
Data
한다면, 예를 들어 gcc has__attribute__((packed))
. 성능에 영향을 주지만 시도해 볼 가치가 있습니다. 그러나packed
패딩을 완전히 허용하지 않으면 알 수 없다는 것을 인정해야 합니다. GCC 의사 는 말한다 :
구조체 또는 공용체 유형 정의에 첨부 된이 속성은 구조 또는 공용체의 각 멤버가 필요한 메모리를 최소화하도록 배치되도록 지정합니다. 열거 형 정의에 첨부되면 가장 작은 정수 유형을 사용해야 함을 나타냅니다.
- 당신이 통제하지 않는다면
Data
적어도std::has_unique_object_representations<T>
당신의 비교가 올바른 결과를 산출 할 수 있는지 말해 줄 수 있습니다.
T가 TriviallyCopyable이고 값이 같은 T 유형의 두 오브젝트가 동일한 오브젝트 표현을 갖는 경우 멤버 상수 값이 true 인 경우. 다른 유형의 경우 값은 false입니다.
그리고 더 :
이 특성은 객체 표현을 바이트 배열로 해싱하여 형식을 올바르게 해시 할 수 있는지 여부를 확인할 수 있도록하기 위해 도입되었습니다.
PS : 나는 패딩을 해결,하지만 희귀 수단 (예에 의해입니다 메모리의 다른 표현으로 인스턴스에 대해 동일하게 비교할 수 유형을 잊지 해달라고 std::string
, std::vector
그리고 많은 다른).
답변
간단히 말해서 : 일반적인 방식으로는 불가능합니다.
문제 memcmp
는 패딩에 임의의 데이터가 포함되어 memcmp
실패 할 수 있다는 것입니다. 패딩이 어디에 있는지 알아낼 수있는 방법이 있다면 해당 비트를 제로 아웃 한 다음 데이터 표현을 비교할 수 있습니다. 멤버가 사소하게 비교 가능한지 여부가 같은지 확인합니다 (즉, std::string
두 개의 문자열이 다른 포인터를 포함하지만 두 개의 문자 배열은 동일합니다. 그러나 구조체 패딩을 얻을 수있는 방법이 없습니다. 컴파일러에게 구조체를 포장하도록 지시 할 수는 있지만 액세스 속도가 느려지고 실제로 작동하지는 않습니다.
이를 구현하는 가장 깨끗한 방법은 모든 멤버를 비교하는 것입니다. 물론 이것은 일반적인 방법으로는 불가능합니다 (C ++ 23 이상에서 컴파일 타임 리플렉션 및 메타 클래스를 얻을 때까지). C ++ 20부터는 기본값을 생성 할 수는 operator<=>
있지만 이것이 멤버 함수로만 가능하므로 다시 적용 할 수는 없다고 생각합니다. 운이 좋고 비교하려는 모든 구조체에 operator==
정의가 있으면 물론 사용할 수 있습니다. 그러나 이것이 보장되지는 않습니다.
편집 : 좋아, 실제로 집계에는 완전히 해킹되고 다소 일반적인 방법이 있습니다. (나는 단지 tuple 로의 변환을 썼다. 이것들은 기본 비교 연산자를 가지고있다). 갓 볼트
답변
C ++ 20은 기본 비교를 지원합니다
#include <iostream>
#include <compare>
struct XYZ
{
int x;
char y;
long z;
auto operator<=>(const XYZ&) const = default;
};
int main()
{
XYZ obj1 = {4,5,6};
XYZ obj2 = {4,5,6};
if (obj1 == obj2)
{
std::cout << "objects are identical\n";
}
else
{
std::cout << "objects are not identical\n";
}
return 0;
}
답변
POD 데이터를 가정하면 기본 할당 연산자는 멤버 바이트 만 복사합니다. (실제로 100 % 확신하지 못합니다. 내 말을 받아들이지 마십시오)
이것을 당신의 이점으로 사용할 수 있습니다 :
template<typename Data>
bool structCmp(Data data1, Data data2) // Data is POD
{
Data tmp;
memcpy(&tmp, &data1, sizeof(Data)); // copy data1 including padding
tmp = data2; // copy data2 only members
return memcmp(&tmp, &data1, sizeof(Data)) == 0;
}
답변
magic_get
도서관 에서 Antony Polukhin의 놀랍도록 악의적 인 부두를 기반으로 해결책을 제시 할 수 있다고 생각합니다 . 복잡한 수업이 아닌 구조체입니다.
이 라이브러리를 사용하면 순수한 형식의 코드로 적절한 유형의 구조체의 다른 필드를 반복 할 수 있습니다. 예를 들어 Antony는 임의의 구조체를 올바른 유형의 출력 스트림에 완전히 일반적으로 스트리밍 할 수 있도록 사용했습니다. 비교가이 접근법의 적용 가능성이있을 수도 있다는 이유가있다.
…하지만 C ++ 14가 필요합니다. 적어도 C ++ 17보다 낫고 나중에 다른 답변에서 제안합니다 😛