C++ 3방향 비교 연산자

Seongcheol Jeon·2024년 12월 7일
0

CPP

목록 보기
36/47
post-thumbnail

객체 간 비교 연산

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같음(이전이나 다음 순서가 아님)0strong_ordering
equivalent동등함(이전이나 다음 순서가 아님)0strong_ordering, weak_ordering, partial_ordering
less보다 작음(이전 순서)-1
greater보다 큼(다음 순서)1
unordered비교할 수 없음-128

강한 비교일 때는 완전히 같을 때 0으로 똑같다고 표현한다. 반면에 약한 비교일 때는 -0+0처럼 비트가 완전히 같지 않아도 0으로 동등하다고 표현한다. 그리고 부동 소수점을 비교할 때는 부분 비교가 이뤄지는데, 피연산자가 NaN 같은 비정상인 부동 소수점일 때 unordered비교할 수 없음을 표현한다.

NaNNaN을 포함한 어떤 숫자와 비교하든 항상 false이다.

0개의 댓글