[C++20 새기능 소개] 삼항 비교 연산자 (<==>)

C++20의 새로운 기능들을 소개하는 시리즈의 첫 번째 글에 오신 것을 환영합니다. 이번 글에서는 프로그래머들의 코딩을 더욱 간결하고 효율적으로 만들어 줄 삼항 비교 연산자(<=>), 일명 우주선 연산자에 대해 알아보겠습니다.

삼항 비교 연산자(<=>)란?

C++20에서 도입된 삼항 비교 연산자(<=>)는 두 객체의 비교를 하나의 연산으로 처리할 수 있게 해주는 강력한 도구입니다. 이 연산자를 사용하면 객체의 <, >, ==, !=, <=, >= 연산자를 하나하나 구현할 필요 없이, 자동으로 생성된 비교 연산자를 활용할 수 있습니다.

왜 <=>를 사용해야 할까요?

기존에는 사용자 정의 객체를 비교할 때 모든 비교 연산자를 수동으로 구현해야 했습니다. 이는 코드의 중복을 초래하고 유지 보수를 어렵게 만들었습니다. <=> 연산자를 사용하면 이러한 문제를 해결하고, 코드의 가독성과 효율성을 높일 수 있습니다.

간단한 예제

기존 방식의 비교 연산자 구현

class Point {
public:
    int x, y;

    bool operator==(const Point& other) const {
        return x == other.x && y == other.y;
    }

    bool operator!=(const Point& other) const {
        return !(*this == other);
    }

    bool operator<(const Point& other) const {
        if (x != other.x)
            return x < other.x;
        return y < other.y;
    }

    // 다른 비교 연산자들도 추가로 구현해야 합니다.
};​

위 코드에서는 Point 클래스의 객체들을 비교하기 위해 여러 연산자들을 직접 구현했습니다.

<=> 연산자를 사용한 개선

#include <compare>

class Point {
public:
    int x, y;

    auto operator<=>(const Point& other) const = default;
};

이제 <=> 연산자를 = default로 선언하면, 컴파일러가 자동으로 모든 비교 연산자들을 생성해줍니다.

<=> 연산자의 작동 방식

1. 자동 생성된 operator<=>의 동작

operator<=>를 = default로 선언하면, 컴파일러는 클래스의 멤버들을 선언된 순서대로 비교하여 연산자를 자동 생성합니다.

auto operator<=>(const Point& other) const = default;

 

위 코드에서 컴파일러는 다음과 같이 동작합니다:

  1. this->x와 other.x를 비교합니다.
  2. x 값이 같지 않으면 그 결과를 반환합니다.
  3. x 값이 같으면 this->y와 other.y를 비교합니다.
  4. 모든 멤버에 대한 비교 결과를 종합하여 최종 결과를 반환합니다.

2. 반환 타입과 비교 카테고리

operator<=>의 반환 타입은 비교되는 멤버들의 타입에 따라 결정됩니다. 일반적으로 기본 자료형(int, double 등)은 std::strong_ordering을 반환합니다.

  • std::strong_ordering: 완전한 순서와 동치성을 강하게 보장합니다.
  • std::weak_ordering: 동치성은 약하게, 순서는 강하게 보장합니다.
  • std::partial_ordering: 부분적인 순서만 보장하며, 비교 불가능한 경우를 허용합니다.

3. 자동 생성되는 비교 연산자들

operator<=>를 = default로 선언하면 다음의 연산자들이 자동으로 생성됩니다:

  • operator==
  • operator!=
  • operator<
  • operator<=
  • operator>
  • operator>=

예제 코드로 살펴보기

#include <compare>
#include <iostream>

class Point {
public:
    int x, y;

    auto operator<=>(const Point& other) const = default;
};

int main() {
    Point p1{1, 2};
    Point p2{2, 3};

    if (p1 < p2) {
        std::cout << "p1이 p2보다 작습니다." << std::endl;
    } else if (p1 == p2) {
        std::cout << "p1과 p2는 같습니다." << std::endl;
    } else {
        std::cout << "p1이 p2보다 큽니다." << std::endl;
    }

    return 0;
}

작동 원리 상세 설명

p1 < p2를 평가할 때:

  • p1.operator<=>(p2)가 호출됩니다.
  • p1.x와 p2.x를 비교합니다: 1 <=> 2 결과는 std::strong_ordering::less입니다.
  • 첫 번째 멤버 비교에서 이미 less 결과가 나왔으므로, p1이 p2보다 작다고 판단합니다.
  • 따라서 if (p1 < p2) 조건이 참이 됩니다.

p1 == p2를 평가할 때:

  • 자동 생성된 operator==를 사용합니다.
  • p1.x == p2.x 결과는 false입니다.
  • 따라서 p1과 p2는 같지 않습니다.

멤버 비교 순서의 중요성

멤버 비교는 선언된 순서대로 이루어집니다. 만약 비교 우선순위를 변경하고 싶다면 멤버 선언 순서를 바꾸거나, operator<=>를 직접 구현해야 합니다.

class Point {
public:
    int x, y;

    // y를 먼저 비교하도록 직접 구현
    auto operator<=>(const Point& other) const {
        if (auto cmp = y <=> other.y; cmp != 0)
            return cmp;
        return x <=> other.x;
    }
};

위 코드에서는 y를 먼저 비교하도록 operator<=>를 직접 구현했습니다.

operator<=>를 직접 구현하는 경우

특정한 비교 로직이 필요할 때는 operator<=>를 직접 구현할 수 있습니다.

class Person {
public:
    std::string name;
    int age;

    // 나이로만 비교
    auto operator<=>(const Person& other) const {
        return age <=> other.age;
    }

    // 이름과 나이를 모두 비교하여 동치성 판단
    bool operator==(const Person& other) const {
        return name == other.name && age == other.age;
    }
};

주의 사항

  • 반환 타입 명시: 직접 구현할 때는 반환 타입을 명시하는 것이 좋습니다.
  • 동치성 연산자: operator==는 자동 생성되지 않으므로, 필요하다면 직접 구현해야 합니다.

상세 정리

  • 자동 생성 조건: 클래스의 모든 멤버들이 비교 가능해야 operator<=>를 = default로 선언할 수 있습니다.
  • 비교 불가능한 멤버: 비교할 수 없는 멤버가 있다면 operator<=>를 직접 구현해야 합니다.
  • 동치성 판단: operator<=>를 = default로 선언하면 operator==도 자동으로 생성됩니다.
  • 멤버의 비교 순서: 멤버들이 선언된 순서대로 비교되므로, 순서를 신경 써야 합니다.

결론

삼항 비교 연산자(<=>)와 = default를 사용하면 객체의 비교 연산을 매우 간단하게 구현할 수 있습니다. 이는 코드의 양을 줄이고, 유지 보수를 용이하게 하며, 실수를 줄이는 데 큰 도움이 됩니다.

 

반응형