3방향 비교 연산자
를 알아보기 전에 객체 비교에 관해 알아보자. 정수형처럼 기본 형식의 데이터를 비교하는 것은 명확하다. 숫자 1은 숫자 2보다 결코 클 수 없다. 하지만 비교할 대상이 클래스나 구조체라면 어떨까?
예를 들어 클래스 객체를 특정 멤버 변수 기준으로 오름차순 정렬한다면 다음 코드처럼 객체를 대상으로 직접 비교 연산을 수행할 수 없다.
// 실행이 불가능한 객체 간 비교 연산
if (obj_a == obj_b) { }
else if (obj_a > obj_b) { }
else { }
이렇게 객체를 비교하려면 ==
, !=
, >
, >=
, <
, <=
등의 연산자를 오버로딩
해야 한다. 그래야만 같은 클래스로 생성한 객체를 비교할 수 있다.
위에서 확인한 것처럼 객체를 비교하려면 꽤 복잡한 과정을 거쳐야 한다. 3방향 비교 연산자(three-way comparison)
는 이처럼 복잡한 과정을 단순하게 해준다. 비교 연산자 6개를 모두 오버로딩할 필요 없이 3방향 비교 연산자 <=>
하나만 오버로딩
하면 된다.
그러면 컴파일러
가 나머지 연산자를 모두 오버로딩
해 준다. 참고로 <=> 연산자
는 우주선 모양과 비슷하다고 해서 흔히 우주선 연산자
라고도 한다.
다음 코드는 정수와 문자로 구성된 구조체 배열에서 원소의 크기를 비교한 예이다. 3방향 비교 연산자
를 사용하지 않는다면 여러 개의 연산자를 오버로딩하여 정수와 문자를 따로따로 비교해야 하지만, 3방향 비교 연산자
를 사용하면 코드를 간결하게 작성할 수 있다.
3방향 비교 연산자
를 사용하려면, 소스에<compare>
헤더를 포함해야 한다.
#include <iostream>
#include <compare>
using std::cout;
using std::endl;
typedef struct _tag {
int number;
char alphabet;
auto operator<=>(const _tag& object) const {
return number <=> object.number;
}
};
// 구조체를 DATA로 사용하기 위한 선언
using DATA = struct _tag;
DATA data_element[5] = { {4, 'a'}, {1, 'c'}, {8, 'b'}, {2, 'z'}, {4, 'd'} };
int main()
{
cout << std::boolalpha << "0번째가 3번째 원소보다 크다 : ";
cout << ((data_element[0] <=> data_element[3]) > 0) << endl;
cout << std::boolalpha << "1번째가 2번째 원소보다 크다 : ";
cout << (data_element[1] > data_element[2]) << endl;
return 0;
}
실행 결과
0번째가 3번째 원소보다 크다 : true
1번째가 2번째 원소보다 크다 : false
코드를 보면 구조체에 있는 정수형 멤버인 number를 비교하는 3방향 비교 연산자
를 오버로딩했다. 오버로딩된 함수에서는 정수형 데이터에 대한 3방향 비교 연산자
를 사용하여 number 멤버에 대한 비교 연산을 수행한다.
그리고 main 함수에서는 data_element[0]
과 data_element[3]
의 값을 <=> 연산자
로 비교한 후 그 결과가 0보다 큰지 평가했다. 결과는 참이므로 true를 출력한다.
3방향 비교 연산자
는 두 피 연산자를 비교해 같으면 0
, 앞쪽 피연산자가 작으면 -
, 크면 1
을 반환한다.
만약 비교할 수 없으면 -128 (unordered)
을 반환한다.
그런데 <=> 연산자
가 실제 반환하는 데이터 형식은 구조체에 정의된 멤버 상수임에 주의해야 한다. 피연산자의 형식에 따라 구조체의 상수 가운데 하나를 반환한다. 예를 들어 크기가 명확한 정수형 연산에서는 강한 비교 결과로 strong_ordering
구조체의 멤버 상수를 반환하고, 부동 소수점 연산에서는 부분 비교 결과로 partial_ordering
의 멤버 상수를 반환한다.
구분 | 구조체 | 허용 비교 연산과 결과 | 비교 |
---|---|---|---|
강한 비교 | std::strong_ordering | >(1), ==(0), <(-1) | 정확한 값이 일치하는 객체 비교 예) 정수, 문자열 등 산술적 일치 |
약한 비교 | std::weak_ordering | >(1), ==(0), <(-1) | 논리적 값이 일치하는 객체 비교 예) 도형, 집합 등 동치 관계 성립 |
부분 비교 | std::partial_ordering | >(1), ==(0), <(-1), 비교 불가(unordered) | 때때로 비교가 불가한 객체 비교 예) 부동 소수점 등 일부 비교 불가 값(std::nan) 포함 비교 |
각각의 구조체에는 비교 결과에 대응하는 멤버 상수가 있다. 다음 표에서 허용 비교 유형이란 해당 상수를 허용하는 구조체를 나타낸다.
상수 | 의미 | 정수 | 허용 비교 유형 |
---|---|---|---|
equal | 같음(이전이나 다음 순서가 아님) | 0 | strong_ordering |
equivalent | 동등함(이전이나 다음 순서가 아님) | 0 | strong_ordering, weak_ordering, partial_ordering |
less | 보다 작음(이전 순서) | -1 | |
greater | 보다 큼(다음 순서) | 1 | |
unordered | 비교할 수 없음 | -128 |
강한 비교일 때는 완전히 같을 때 0
으로 똑같다
고 표현한다. 반면에 약한 비교일 때는 -0
과 +0
처럼 비트가 완전히 같지 않아도 0
으로 동등하다
고 표현한다. 그리고 부동 소수점을 비교할 때는 부분 비교가 이뤄지는데, 피연산자가 NaN
같은 비정상인 부동 소수점일 때 unordered
로 비교할 수 없음
을 표현한다.
NaN
은NaN
을 포함한 어떤 숫자와 비교하든 항상 false이다.