C ++에서 일반 구조체를 비교하는 방법은 무엇입니까?


13

일반적인 방식으로 구조체를 비교하고 싶습니다. 이와 같은 작업을 수행했습니다 (실제 소스를 공유 할 수 없으므로 필요한 경우 자세한 내용을 요청하십시오).

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에 붙어 있습니다. 앞에서 언급 했어야했는데 ...


이것이 실패하는 예를 보여줄 수 있습니까? 패딩은 한 유형의 모든 인스턴스에 대해 동일해야합니다.
idclev 463035818

1
@ idclev463035818 패딩은 지정되어 있지 않습니다. 패딩은 가치가 있다고 생각할 수 없으며 그것을 읽는 것이 UB라고 생각합니다 (마지막 부분은 확실하지 않음).
François Andrieux

@ idclev463035818 패딩은 메모리의 동일한 상대 위치에 있지만 다른 데이터를 가질 수 있습니다. 컴파일러는 구조체를 정상적으로 사용할 때 버려 지므로 컴파일러가 0을 귀찮게하지 않을 수 있습니다.
NO_NAME

2
@ idclev463035818 패딩의 크기는 같습니다. 패딩을 구성하는 비트의 상태는 무엇이든 될 수 있습니다. memcmp패딩 비트를 비교에 포함시킬 때 .
François Andrieux

1
나는 Yksisarvinen에 동의합니다 ... 구조체를 사용하지 않고 클래스를 사용하고 == 연산자를 . 사용하는 memcmp것은 신뢰할 수 없으며 조만간 "다른 클래스와는 조금 다른 클래스"를 사용해야하는 클래스를 다룰 것입니다. 연산자로 구현하는 것이 매우 깨끗하고 효율적입니다. 실제 동작은 다형성이지만 소스 코드는 깨끗하고 명확합니다.
마이크 로빈슨

답변:


7

아니요, memcmp이 작업에는 적합하지 않습니다. 그리고 C ++의 리플렉션은이 시점 에서이 작업을 수행하기에 충분하지 않습니다 (이미 수행 할 수있을만큼 강한 리플렉션을 지원하는 실험용 컴파일러가 있으며 에는 필요한 기능이있을 수 있음).

내장 리플렉션이 없으면 문제를 해결하는 가장 쉬운 방법은 수동 리플렉션을 수행하는 것입니다.

이것을 가지고 :

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 );
}

를위한 , 후 :

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간단합니다. 에서 반복은 성가신; 이것은 유용합니다 :

#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 ) )

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::arrayrefl_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) )

그러나 그것은 그것을 통한 재귀를 지원하지 않습니다.


수동 리플렉션으로 이러한 유형의 솔루션을 추구하고 싶습니다. 제공 한 코드가 C ++ 11에서 작동하지 않는 것 같습니다. 당신이 나를 도울 수있는 기회가 있습니까?
Fredrik Enetorp

1
이것이 C ++ 11에서 작동하지 않는 이유는 뒤에 반환 유형이 없기 때문입니다 as_tie. C ++ 14부터는 자동으로 추론됩니다. auto as_tie (some_struct const & s) -> decltype(std::tie(s.x, s.d1, s.d2, s.c));C ++ 11에서 사용할 수 있습니다 . 또는 반환 유형을 명시 적으로 명시하십시오.
Darhuuk

1
@FredrikEnetorp 고정 및 쓰기 쉬운 매크로. 완전히 재귀 적으로 작동하도록하는 작업 (서브 구조체가 as_tie지원하는 자동 구조체 ) 및 배열 멤버를 지원하는 작업은 자세하지 않지만 가능합니다.
Yakk-Adam Nevraumont

감사합니다. 나는 끔찍한 매크로를 약간 다르게했지만 기능적으로 동일했습니다. 하나만 더 별도의 헤더 파일로 비교를 일반화하고 다양한 모의 테스트 파일에 포함하려고합니다. `as_tie (Test1 const &) '의 다중 정의를 인라인하려고하는데 작동하지 않습니다.
Fredrik Enetorp

1
@FredrikEnetorp inline키워드는 다중 정의 오류를 제거해야합니다. 최소한의 재현 가능한 예
Yakk-Adam Nevraumont

7

패딩이 이런 식으로 임의의 유형을 비교하는 방식에 맞는 것이 맞습니다.

취할 수있는 조치가 있습니다 :

  • 당신이 통제 Data한다면, 예를 들어 gcc has __attribute__((packed)). 성능에 영향을 주지만 시도해 볼 가치가 있습니다. 그러나 packed패딩을 완전히 허용하지 않으면 알 수 없다는 것을 인정해야 합니다. GCC 의사 는 말한다 :

구조체 또는 공용체 유형 정의에 첨부 된이 속성은 구조 또는 공용체의 각 멤버가 필요한 메모리를 최소화하도록 배치되도록 지정합니다. 열거 형 정의에 첨부되면 가장 작은 정수 유형을 사용해야 함을 나타냅니다.

T가 TriviallyCopyable이고 값이 같은 T 유형의 두 오브젝트가 동일한 오브젝트 표현을 갖는 경우 멤버 상수 값이 true 인 경우. 다른 유형의 경우 값은 false입니다.

그리고 더 :

이 특성은 객체 표현을 바이트 배열로 해싱하여 형식을 올바르게 해시 할 수 있는지 여부를 확인할 수 있도록하기 위해 도입되었습니다.

PS : 나는 패딩을 해결,하지만 희귀 수단 (예에 의해입니다 메모리의 다른 표현으로 인스턴스에 대해 동일하게 비교할 수 유형을 잊지 해달라고 std::string, std::vector그리고 많은 다른).


1
나는이 답변을 좋아한다. 이 유형 특성을 사용하면 SFINAE를 사용 memcmp하여 패딩이없는 구조체 에 사용 하고 operator==필요할 때만 구현할 수 있습니다.
Yksisarvinen

알았어 고마워. 이를 통해 수동 반사를 수행해야한다는 결론을 내릴 수 있습니다.
Fredrik Enetorp

6

간단히 말해서 : 일반적인 방식으로는 불가능합니다.

문제 memcmp는 패딩에 임의의 데이터가 포함되어 memcmp실패 할 수 있다는 것입니다. 패딩이 어디에 있는지 알아낼 수있는 방법이 있다면 해당 비트를 제로 아웃 한 다음 데이터 표현을 비교할 수 있습니다. 멤버가 사소하게 비교 가능한지 여부가 같은지 확인합니다 (즉, std::string두 개의 문자열이 다른 포인터를 포함하지만 두 개의 문자 배열은 동일합니다. 그러나 구조체 패딩을 얻을 수있는 방법이 없습니다. 컴파일러에게 구조체를 포장하도록 지시 할 수는 있지만 액세스 속도가 느려지고 실제로 작동하지는 않습니다.

이를 구현하는 가장 깨끗한 방법은 모든 멤버를 비교하는 것입니다. 물론 이것은 일반적인 방법으로는 불가능합니다 (C ++ 23 이상에서 컴파일 타임 리플렉션 및 메타 클래스를 얻을 때까지). C ++ 20부터는 기본값을 생성 할 수는 operator<=>있지만 이것이 멤버 함수로만 가능하므로 다시 적용 할 수는 없다고 생각합니다. 운이 좋고 비교하려는 모든 구조체에 operator==정의가 있으면 물론 사용할 수 있습니다. 그러나 이것이 보장되지는 않습니다.

편집 : 좋아, 실제로 집계에는 완전히 해킹되고 다소 일반적인 방법이 있습니다. (나는 단지 tuple 로의 변환을 썼다. 이것들은 기본 비교 연산자를 가지고있다). 갓 볼트


좋은 해킹! 불행히도 C ++ 11에 갇혀있어 사용할 수 없습니다.
Fredrik Enetorp

2

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;
}

1
이 기능은 매우 유용한 기능이지만 질문에 답변되지 않습니다. OP는 "사용 된 구조체를 수정할 수 없습니다"라고 말했는데, 이는 C ++ 20 기본 동등 연산자를 사용할 수 있어도 기본값 ==또는 <=>연산자 만 수행 할 수 있기 때문에 OP에서이를 사용할 수 없음을 의미합니다. 수업 범위에서.
Nicol Bolas

Nicol Bolas가 말했듯이 구조체를 수정할 수 없습니다.
Fredrik Enetorp

1

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; 
}

@walnut 당신은 끔찍한 대답이 맞습니다. 하나를 다시 썼습니다.
코스타스

표준은 할당이 패딩 바이트를 건드리지 않도록 보장합니까? 기본 유형의 동일한 값에 대한 여러 객체 표현에 대한 우려도 여전히 있습니다.
호두

@walnut 나는 믿는다 는 않습니다 .
코스타스

1
해당 링크의 최상위 답변 아래에있는 주석은 그렇지 않은 것으로 나타납니다. 대답 자체는 패딩이 있다고 할 필요 복사,하지만이 것을 할 수 musn't . 그래도 확실하지 않습니다.
호두

나는 지금 그것을 테스트했지만 작동하지 않습니다. 할당은 채움 바이트를 그대로 두지 않습니다.
Fredrik Enetorp

0

magic_get도서관 에서 Antony Polukhin의 놀랍도록 악의적 인 부두를 기반으로 해결책을 제시 할 수 있다고 생각합니다 . 복잡한 수업이 아닌 구조체입니다.

이 라이브러리를 사용하면 순수한 형식의 코드로 적절한 유형의 구조체의 다른 필드를 반복 할 수 있습니다. 예를 들어 Antony는 임의의 구조체를 올바른 유형의 출력 스트림에 완전히 일반적으로 스트리밍 할 수 있도록 사용했습니다. 비교가이 접근법의 적용 가능성이있을 수도 있다는 이유가있다.

...하지만 C ++ 14가 필요합니다. 적어도 C ++ 17보다 낫고 나중에 다른 답변에서 제안합니다 :-P

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.