'튜플'과 '넥타이'를 통해 비교 연산자를 구현하는 것이 좋습니다.


98

(참고 : tuple그리고 tieBoost 또는 C ++ 11에서 가져올 수 있습니다.)
요소가 두 개인 작은 구조체를 작성할 때 strict-weak-ordering std::pair과 같이 해당 데이터 유형에 대해 모든 중요한 작업이 이미 수행 되었기 때문에를 선택하는 경향이 있습니다. operator<.
단점은 거의 쓸모없는 변수 이름입니다. 나 자신이 그 창조하더라도 typedef, 나는 2 일 후 기억하지 않습니다 first어떤 second그들은 동일한 유형의 모두, 특히이었다 정확하게. 중첩 pairs가 상당히 짜증나 기 때문에 두 개 이상의 구성원의 경우 더욱 악화됩니다 .
다른 옵션은tuple, Boost 또는 C ++ 11에서 왔지만 실제로 더 멋지고 명확하게 보이지는 않습니다. 그래서 필자는 필요한 비교 연산자를 포함하여 직접 구조체 작성으로 돌아갑니다.
특히는 operator<매우 번거로울 수 있으므로 다음에 대해 정의 된 작업에 의존하여이 모든 혼란을 피할 수 있다고 생각했습니다 tuple.

예를 operator<들어 strict-weak-ordering의 경우 :

bool operator<(MyStruct const& lhs, MyStruct const& rhs){
  return std::tie(lhs.one_member, lhs.another, lhs.yet_more) <
         std::tie(rhs.one_member, rhs.another, rhs.yet_more);
}

( tietupleT&전달 인자에서 참조.)


편집 : @DeadMG가 개인적으로 상속하라는 제안은 tuple나쁜 것은 아니지만 몇 가지 단점이 있습니다.

  • 운영자가 독립적 인 경우 (아마도 친구 일 수 있음) 공개적으로 상속해야합니다.
  • 캐스팅을 사용하면 내 함수 / 연산자 ( operator=특히)를 쉽게 우회 할 수 있습니다.
  • 으로 tie그들이 순서에 대한 문제가되지 않는 경우 솔루션, 나는 특정 회원을 남길 수 있습니다

이 구현에 고려해야 할 단점이 있습니까?


1
나에게 완벽하게 합리적으로 보인다 ...
ildjarn 2011-06-02

1
그것이 풀리지 않더라도 그것은 매우 영리한 아이디어입니다. 나는 이것을 조사해야 할 것입니다.
templatetypedef

이것은 꽤 합리적으로 보입니다. 내가 생각할 수있는 유일한 함정은 tie비트 필드 멤버에 적용 할 수 없다는 것입니다.
Ise Wisteria

4
이 아이디어가 좋아요! 경우 tie(...)호출이 여러 사업자에 중복 될 예정 (=, ==, <, 등) 당신은 개인 인라인 방법을 쓸 수있는 make_tuple(...)것을 캡슐화에 다음과 같이 여러 다른 장소에서 호출 return lhs.make_tuple() < rhs.make_tuple();(그러나 리턴 타입에서 그 방법은 선언하는 것이 재미있을 수 있습니다!)
aldo

13
@aldo : 구조에 C ++ 14! auto tied() const{ return std::tie(the, members, here); }
Xeo

답변:


61

이것은 확실히 직접 굴리는 것보다 올바른 연산자를 작성하는 것을 더 쉽게 만들 것입니다. 프로파일 링이 비교 작업이 애플리케이션의 시간 소모적 인 부분임을 보여주는 경우에만 다른 접근 방식을 고려한다고 말하고 싶습니다. 그렇지 않으면 유지 관리의 용이성이 성능 문제보다 중요합니다.


17
나는 tuple<>'s operator<가 손으로 쓴 것보다 느리다 는 경우를 상상할 수 없습니다 .
ildjarn 2011 년

51
한 번 똑같은 아이디어를 얻었고 몇 가지 실험을했습니다. 컴파일러 가 튜플 및 참조와 관련된 모든 것을 인라인하고 최적화 하여 손으로 작성한 코드와 거의 동일한 어셈블리를 내 보낸 것을 보고 놀랐습니다 .
JohannesD

7
@JohannesD : 그 증언을 지원할 수있는, 같은 회 한
sehe

이것은 엄격한 약한 주문을 보장합니까 ? 어떻게?
CinCout

5

나는이 같은 문제를 건너 왔고 내 솔루션은 c ++ 11 가변 템플릿을 사용합니다. 다음은 코드입니다.

.h 부분 :

/***
 * Generic lexicographical less than comparator written with variadic templates
 * Usage:
 *   pass a list of arguments with the same type pair-wise, for intance
 *   lexiLessthan(3, 4, true, false, "hello", "world");
 */
bool lexiLessthan();

template<typename T, typename... Args>
bool lexiLessthan(const T &first, const T &second, Args... rest)
{
  if (first != second)
  {
    return first < second;
  }
  else
  {
    return lexiLessthan(rest...);
  }
}

인수가없는 기본 케이스에 대한 .cpp :

bool lexiLessthan()
{
  return false;
}

이제 귀하의 예는 다음과 같습니다.

return lexiLessthan(
    lhs.one_member, rhs.one_member, 
    lhs.another, rhs.another, 
    lhs.yet_more, rhs.yet_more
);

여기에 비슷한 솔루션을 넣었지만! = 연산자가 필요하지 않습니다. stackoverflow.com/questions/11312448/…
steviekm3

3

제 생각에는 여전히 해결과 동일한 문제를 std::tuple다루지 않고 있습니다. 즉, 각 멤버 변수의 개수와 이름을 모두 알아야하며 함수에서 두 번 복제합니다. private상속을 선택할 수 있습니다 .

struct somestruct : private std::tuple<...> {
    T& GetSomeVariable() { ... }
    // etc
};

이 방법은입니다 조금 조금 더 시작하는 혼란의,하지만 당신은 단지 대신 당신이 과부하하고자하는 모든 운영자에 대한 모든 장소에서, 한 곳에서 변수와 이름을 유지하고 있습니다.


3
그래서 나는 T& one_member(){ return std::get<0>(*this); }등 의 변수에 명명 된 접근자를 사용할 것 입니까? 하지만 내가 가지고있는 각 "멤버"에 대해 const 및 non-const 버전에 대한 오버로드를 포함하여 그러한 메서드를 제공 할 필요가 없을까요?
Xeo 2011-06-02

@Xeo 명명 된 접근자가 실제 변수를 만드는 것보다 더 많은 작업이 필요하다고 생각하지 않습니다. 어느 쪽이든 각 변수에 대해 별도의 이름을 가져야합니다. const / non-const에 대한 중복이 있다고 가정합니다. 그러나이 모든 작업을 템플릿으로 만들 수 있습니다.
리 루비 에르

1

두 개 이상의 연산자 오버로드 또는 튜플에서 여러 메서드를 사용하려는 경우 튜플을 클래스의 구성원으로 만들거나 튜플에서 파생하는 것이 좋습니다. 그렇지 않으면, 당신이하는 일은 훨씬 더 많은 일입니다. 둘 사이에 결정할 때, 대답에 중요한 질문은 : 음주 당신이 당신의 클래스가 할 튜플? 그렇지 않다면 튜플을 포함하고 위임을 사용하여 인터페이스를 제한하는 것이 좋습니다.

튜플 멤버의 "이름을 바꾸는"접근자를 만들 수 있습니다.


OP의 질문을 " 합리적으로 operator<사용하여 내 클래스를 구현 std::tie하고 있습니까?" 라는 의미로 읽었습니다. 이 답변이 그 질문과 어떤 관련이 있는지 이해하지 못합니다.
ildjarn 2011 년

@ildjarn 내가 여기에 게시하지 않은 댓글이 있습니다. 더 잘 읽을 수 있도록 모든 것을 컴파일했습니다.
Lee Louviere 2011 년
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.