C++20의 spaceship operator(우주선 연산자)로 객체 비교 로직 단순화하기

C++20에서 새롭게 도입된 spaceship operator(<=>) 는 객체 비교 연산을 단순화하는 강력한 기능입니다. 기존 C++에서는 비교 연산자(==, !=, <, >, <=, >=)를 직접 오버로딩해야 했으며, 모든 연산자를 구현하는 과정에서 중복 코드가 발생하는 문제가 있었습니다.

spaceship operator는 이러한 불편함을 해결하여 단 한 줄의 코드만으로 비교 연산자를 자동 생성할 수 있도록 합니다. 이를 통해 코드 가독성이 높아지고 유지보수가 쉬워지며, 표준 라이브러리와의 연동도 더욱 직관적으로 이루어집니다.

이 기사에서는 spaceship operator의 개념과 구현 방법, 비교 연산 자동 생성 원리, 사용자 정의 객체에 적용하는 방법을 자세히 살펴봅니다. 또한, 기존 비교 연산 방식과의 차이를 분석하고, std::sort 등의 표준 알고리즘에서 spaceship operator를 활용하는 방법도 함께 다룹니다.

목차
  1. spaceship operator란?
    1. spaceship operator의 기본 문법
    2. spaceship operator의 반환값
  2. 기존 비교 연산자의 한계
    1. 모든 비교 연산자를 직접 구현해야 하는 문제
    2. 유지보수의 어려움
  3. spaceship operator의 구현 방식
    1. 기본적인 spaceship operator 구현
    2. 수동으로 spaceship operator 정의하기
    3. 기본 제공되는 비교 타입
    4. spaceship operator 도입의 장점
  4. 비교 연산 자동 생성의 원리
    1. 자동 생성되는 비교 연산자
    2. 예제: 기본적인 자동 생성 원리
    3. 연산자 자동 생성 규칙
    4. 예제: operator<=> 없이 operator== 만 정의할 경우
    5. 비교 연산 자동 생성의 장점
    6. 요약
  5. 강한, 약한, 부분 순서 비교
    1. 비교 순서의 개념
    2. 1. 강한 순서 비교 (`std::strong_ordering`)
    3. 2. 약한 순서 비교 (`std::weak_ordering`)
    4. 3. 부분 순서 비교 (`std::partial_ordering`)
    5. 비교 순서 정리
    6. spaceship operator와 비교 타입의 조합
    7. 예제: 비교 타입 명시적으로 설정
    8. 요약
  6. 사용자 정의 클래스에서 spaceship operator 적용하기
    1. 기본 구조체에 spaceship operator 적용하기
    2. 사용자 정의 비교 기준 적용하기
    3. 부분 순서 비교 적용하기
    4. 객체 포인터 비교
    5. 요약
  7. spaceship operator와 std::sort 활용
    1. std::sort와 spaceship operator 사용하기
    2. 사용자 지정 정렬 기준 적용하기
    3. 1. 람다 함수 활용
    4. 2. 사용자 지정 비교 연산자 제공
    5. 정렬 기준이 여러 개일 경우
    6. 요약
  8. 유용한 예제와 실습 문제
    1. 예제 1: 3D 벡터 비교
    2. 예제 2: 정렬 가능한 학생 객체
    3. 예제 3: 복잡한 객체 비교 (다중 기준)
    4. 실습 문제
    5. 요약
  9. 요약
    1. 핵심 정리
    2. 결론

spaceship operator란?


C++20에서 새롭게 추가된 spaceship operator(<=>) 는 객체 간의 비교 연산을 간결하게 처리하는 기능을 제공합니다. 기존 C++에서는 ==, !=, <, >, <=, >= 등의 비교 연산자를 각각 구현해야 했으나, spaceship operator를 활용하면 단 한 줄의 코드만으로 모든 비교 연산을 자동으로 생성할 수 있습니다.

spaceship operator의 기본 문법


spaceship operator는 세 방향 비교(three-way comparison) 를 수행하는 연산자로, 다음과 같은 형태로 사용됩니다.

#include <iostream>
#include <compare> // spaceship operator를 사용하기 위해 필요

struct Point {
    int x, y;
    auto operator<=>(const Point&) const = default; // spaceship operator 사용
};

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

    if (p1 < p2) {
        std::cout << "p1이 p2보다 작습니다.\n";
    } else {
        std::cout << "p1이 p2보다 크거나 같습니다.\n";
    }

    return 0;
}

위 코드에서 operator<=> 를 정의하면 ==, !=, <, >, <=, >= 연산자가 자동으로 생성됩니다. 따라서, 비교 연산자를 일일이 오버로딩할 필요 없이 간편하게 객체 비교가 가능합니다.

spaceship operator의 반환값


spaceship operator는 비교 결과를 std::strong_ordering, std::weak_ordering, std::partial_ordering 과 같은 타입으로 반환합니다. 이는 비교 연산의 강도(strength)를 결정하며, 이에 대한 자세한 내용은 후속 섹션에서 다루겠습니다.

spaceship operator를 활용하면 코드의 간결성, 유지보수성, 성능 최적화를 모두 향상할 수 있습니다. 다음 섹션에서는 기존 비교 연산자의 한계를 살펴보고 spaceship operator의 장점을 더욱 깊이 이해해 보겠습니다.

기존 비교 연산자의 한계

C++20 이전까지 객체 간 비교를 수행하려면 모든 비교 연산자(==, !=, <, >, <=, >=)를 직접 오버로딩해야 했습니다. 이러한 방식은 코드 중복이 많아지고 유지보수가 어려워지는 단점이 있었습니다.

모든 비교 연산자를 직접 구현해야 하는 문제


예를 들어, C++17까지는 아래와 같이 각 비교 연산자를 개별적으로 정의해야 했습니다.

#include <iostream>

struct Point {
    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 {
        return x < other.x || (x == other.x && y < other.y);
    }

    bool operator>(const Point& other) const {
        return other < *this;
    }

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

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

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

    if (p1 < p2) {
        std::cout << "p1이 p2보다 작습니다.\n";
    }
}

위 코드에서는 operator==, operator!=, operator<, operator>, operator<=, operator>= 를 모두 정의해야 합니다.
이는 비교 연산자가 많아질수록 코드 중복이 심각해지고, 새로운 필드가 추가될 경우 모든 연산자를 수정해야 하는 문제가 발생합니다.

유지보수의 어려움

  • 새로운 비교 기준이 필요하면 모든 비교 연산자를 수정해야 합니다.
  • 코드 중복으로 인해 버그 발생 가능성이 증가합니다.
  • 개발자가 실수로 operator!= 또는 operator<= 등을 정의하지 않으면 비교 연산이 올바르게 동작하지 않을 수 있습니다.

이러한 문제점을 해결하기 위해 C++20에서는 spaceship operator(<=>) 가 도입되었습니다. spaceship operator를 사용하면 한 줄의 코드만으로 모든 비교 연산자를 자동 생성할 수 있어 코드 중복을 줄이고 유지보수를 쉽게 할 수 있습니다. 다음 섹션에서는 spaceship operator의 구현 방식과 그 장점을 자세히 살펴보겠습니다.

spaceship operator의 구현 방식

C++20의 spaceship operator(<=>) 를 사용하면 단 한 줄의 코드만으로 모든 비교 연산자를 자동으로 생성할 수 있습니다. 기존의 비교 연산자 오버로딩 방식과 달리, spaceship operator는 한 번만 정의하면 ==, !=, <, >, <=, >= 연산자를 자동 생성하는 장점이 있습니다.

기본적인 spaceship operator 구현


아래는 spaceship operator를 사용한 비교 연산자 오버로딩 예제입니다.

#include <iostream>
#include <compare> // spaceship operator를 사용하기 위해 필요

struct Point {
    int x, y;

    // spaceship operator 적용
    auto operator<=>(const Point&) const = default;
};

int main() {
    Point p1{3, 4}, p2{3, 5};

    if (p1 < p2) {
        std::cout << "p1이 p2보다 작습니다.\n";
    }

    if (p1 == p2) {
        std::cout << "p1과 p2는 같습니다.\n";
    }

    return 0;
}

위 코드에서 operator<=>default 로 선언하면 모든 비교 연산자(==, !=, <, >, <=, >=)가 자동으로 생성됩니다. 이를 통해 코드 중복이 제거되며, 새로운 멤버 변수를 추가할 때도 비교 연산자를 따로 수정할 필요가 없습니다.

수동으로 spaceship operator 정의하기


= default 를 사용하지 않고 직접 spaceship operator를 구현할 수도 있습니다.

#include <iostream>
#include <compare>

struct Point {
    int x, y;

    // spaceship operator를 직접 정의
    std::strong_ordering operator<=>(const Point& other) const {
        if (auto cmp = x <=> other.x; cmp != 0) return cmp;
        return y <=> other.y;
    }
};

int main() {
    Point p1{3, 4}, p2{3, 5};

    if (p1 < p2) {
        std::cout << "p1이 p2보다 작습니다.\n";
    }

    if (p1 == p2) {
        std::cout << "p1과 p2는 같습니다.\n";
    }

    return 0;
}

위 코드에서는 x 좌표를 먼저 비교하고, 값이 동일하면 y 좌표를 비교하도록 spaceship operator를 직접 정의했습니다. 이를 통해 사용자가 원하는 비교 로직을 자유롭게 설정할 수 있습니다.

기본 제공되는 비교 타입


spaceship operator는 비교 연산의 성격에 따라 세 가지 타입을 지원합니다.

비교 타입의미반환 타입
강한 순서 (Strong Ordering)완전한 순서 비교가 가능할 때std::strong_ordering
약한 순서 (Weak Ordering)동등한 값이 있는 경우 비교 가능할 때std::weak_ordering
부분 순서 (Partial Ordering)비교가 항상 가능하지 않을 때std::partial_ordering

각 비교 타입의 차이점은 이후 섹션에서 더 자세히 다룹니다.

spaceship operator 도입의 장점

  1. 코드 중복 제거: operator==, operator!=, operator<, operator>, operator<=, operator>= 를 개별적으로 정의할 필요가 없습니다.
  2. 자동 비교 연산 생성: 단 한 줄의 코드(auto operator<=> = default;)만으로 모든 비교 연산이 가능해집니다.
  3. 가독성과 유지보수성 향상: 새로운 필드를 추가해도 비교 연산자를 수정할 필요가 없습니다.
  4. 성능 최적화: 컴파일러가 최적화된 비교 연산을 자동으로 생성하여 성능이 향상될 수 있습니다.

다음 섹션에서는 spaceship operator가 자동으로 생성하는 비교 연산자들의 동작 원리를 살펴보겠습니다.

비교 연산 자동 생성의 원리

C++20에서 spaceship operator(<=>)를 사용하면 한 줄의 코드만으로 비교 연산자가 자동 생성됩니다. 기존에는 ==, !=, <, >, <=, >= 연산자를 각각 정의해야 했지만, spaceship operator를 적용하면 자동으로 필요한 비교 연산자가 생성됩니다.

자동 생성되는 비교 연산자


spaceship operator는 다음 규칙에 따라 비교 연산자를 자동 생성합니다.

  1. operator<=> 만 정의하면 ==, !=, <, >, <=, >= 가 자동 생성됨.
  2. operator== 만 정의하면 != 이 자동 생성됨.
  3. operator<=>operator== 를 함께 정의하면 모든 비교 연산자가 자동 생성됨.

예제: 기본적인 자동 생성 원리

#include <iostream>
#include <compare>

struct Point {
    int x, y;
    auto operator<=>(const Point&) const = default; // spaceship operator 적용
};

int main() {
    Point p1{3, 4}, p2{3, 5};

    std::cout << std::boolalpha;
    std::cout << "p1 == p2: " << (p1 == p2) << "\n"; // 자동 생성된 == 연산자 사용
    std::cout << "p1 < p2: " << (p1 < p2) << "\n";   // 자동 생성된 < 연산자 사용
}

출력 결과:

p1 == p2: false
p1 < p2: true

위 코드에서는 operator<=>default 로 정의했기 때문에 모든 비교 연산자가 자동으로 생성됩니다.


연산자 자동 생성 규칙


spaceship operator가 적용되었을 때 자동으로 생성되는 연산자들의 관계는 아래 표와 같습니다.

정의된 연산자자동 생성되는 연산자
operator<=>==, !=, <, >, <=, >=
operator==!=
operator<=> + operator==모든 비교 연산자

이 원리를 활용하면 코드 중복을 최소화하면서도 모든 비교 연산을 지원할 수 있습니다.

예제: operator<=> 없이 operator== 만 정의할 경우

#include <iostream>

struct Point {
    int x, y;

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

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

    std::cout << std::boolalpha;
    std::cout << "p1 == p2: " << (p1 == p2) << "\n"; // 직접 정의한 ==
    std::cout << "p1 != p2: " << (p1 != p2) << "\n"; // 자동 생성된 !=
}

출력 결과:

p1 == p2: true
p1 != p2: false

위 코드에서는 operator== 만 정의했지만 != 연산자가 자동으로 생성되었습니다. 그러나 <, >, <=, >= 연산자는 정의되지 않기 때문에 컴파일 오류가 발생합니다. 이를 방지하려면 operator<=> 를 사용해야 합니다.


비교 연산 자동 생성의 장점

  1. 코드 유지보수 간편화
  • 한 줄의 코드(auto operator<=> = default;)만 추가하면 모든 비교 연산이 자동 생성됩니다.
  • 새로운 멤버 변수를 추가해도 비교 연산자를 다시 작성할 필요가 없습니다.
  1. 중복 코드 감소
  • 기존 방식에서는 ==, !=, <, >, <=, >= 를 모두 직접 구현해야 했습니다.
  • spaceship operator를 사용하면 불필요한 코드 중복을 줄이고 가독성을 높일 수 있습니다.
  1. 성능 최적화
  • 컴파일러가 자동으로 비교 연산을 생성하므로 최적화된 코드가 생성될 가능성이 높아집니다.

요약

  • spaceship operator를 사용하면 operator<=> 만 정의해도 모든 비교 연산자를 자동 생성할 수 있습니다.
  • operator== 만 정의하면 != 연산자만 자동 생성되며, <, >, <=, >= 는 자동 생성되지 않습니다.
  • 코드 중복을 줄이고 유지보수를 쉽게 만들며, 컴파일러의 최적화를 유도할 수 있습니다.

다음 섹션에서는 spaceship operator에서 지원하는 강한 순서, 약한 순서, 부분 순서 비교(strong_ordering, weak_ordering, partial_ordering) 에 대해 자세히 살펴보겠습니다.

강한, 약한, 부분 순서 비교

C++20의 spaceship operator(<=>) 는 객체 간의 비교 연산을 단순화할 뿐만 아니라, 비교 연산의 성질에 따라 강한 순서(strong ordering), 약한 순서(weak ordering), 부분 순서(partial ordering) 를 지원합니다. 각 순서의 차이를 이해하면, 특정 데이터 구조나 연산에 적절한 비교 방식을 적용할 수 있습니다.


비교 순서의 개념

비교 순서의미사용 예시
강한 순서 (Strong Ordering)완벽한 순서가 존재하며, a == b 면 반드시 동일한 값을 가짐.정수형, 문자열 비교
약한 순서 (Weak Ordering)a == b 일 때 논리적으로 동등하지만, 내부적으로 다를 수 있음.사용자 정의 객체 비교
부분 순서 (Partial Ordering)일부 값 간에는 비교가 불가능할 수 있음.부동소수점 NaN 비교

1. 강한 순서 비교 (`std::strong_ordering`)


강한 순서(strong ordering)모든 비교 연산자가 일관된 결과를 보장하는 경우 사용됩니다. 즉, a == b 이면 반드시 같은 값을 가지며, <, >, <=, >= 연산도 항상 논리적으로 올바른 결과를 반환합니다.

#include <iostream>
#include <compare>

struct IntWrapper {
    int value;
    auto operator<=>(const IntWrapper&) const = default; // strong_ordering 사용
};

int main() {
    IntWrapper a{5}, b{10};

    if (a < b) std::cout << "a가 b보다 작음\n";
    if (a == a) std::cout << "a와 a는 같음\n";
}

출력 결과:

a가 b보다 작음
a와 a는 같음
  • 정수형 int, char 등의 기본 타입은 std::strong_ordering 을 따릅니다.
  • = default 를 사용하면 강한 순서 비교가 기본 적용됩니다.

2. 약한 순서 비교 (`std::weak_ordering`)


약한 순서(weak ordering)a == b 일 때 내부적으로 다를 수 있음을 의미합니다. 예를 들어, 두 개의 다른 객체가 논리적으로는 같지만 내부적으로 미묘한 차이가 있을 때 사용할 수 있습니다.

#include <iostream>
#include <compare>

struct Person {
    std::string name;
    int age;

    std::weak_ordering operator<=>(const Person& other) const {
        return age <=> other.age; // 나이 기준 비교
    }
};

int main() {
    Person p1{"Alice", 30}, p2{"Bob", 30};

    if (p1 == p2) std::cout << "두 사람은 같은 나이입니다.\n";
}

출력 결과:

두 사람은 같은 나이입니다.
  • p1p2이름이 다르지만 같은 나이이므로 논리적으로는 같다고 판단합니다.
  • 약한 순서는 논리적으로 동등하지만 완전히 동일하지는 않은 경우에 사용됩니다.

3. 부분 순서 비교 (`std::partial_ordering`)


부분 순서(partial ordering)일부 값에 대해 비교가 불가능할 수도 있는 경우에 사용됩니다. 대표적인 예가 부동소수점 NaN(Not-a-Number) 비교입니다.

#include <iostream>
#include <compare>
#include <cmath>

struct FloatWrapper {
    double value;

    std::partial_ordering operator<=>(const FloatWrapper& other) const {
        if (std::isnan(value) || std::isnan(other.value))
            return std::partial_ordering::unordered; // 비교 불가능
        return value <=> other.value;
    }
};

int main() {
    FloatWrapper a{NAN}, b{5.0};

    if (a < b) std::cout << "a가 b보다 작음\n";
    else if (a > b) std::cout << "a가 b보다 큼\n";
    else std::cout << "a와 b는 비교할 수 없음\n";
}

출력 결과:

a와 b는 비교할 수 없음
  • NAN 값이 포함되면 <=> 연산이 비교 불가능(unordered) 상태를 반환합니다.
  • 부분 순서는 비교할 수 없는 값이 존재하는 경우에 사용됩니다.

비교 순서 정리

비교 방식의미예제
강한 순서 (std::strong_ordering)모든 비교 연산이 항상 유효정수, 문자열
약한 순서 (std::weak_ordering)동등한 값이 내부적으로 다를 수 있음객체 비교 (나이 같은 사람)
부분 순서 (std::partial_ordering)일부 값 간의 비교가 불가능할 수도 있음부동소수점 NaN

spaceship operator와 비교 타입의 조합


spaceship operator를 사용할 때, 비교 연산의 타입을 명확히 설정하면 코드의 의도를 더욱 정확하게 표현할 수 있습니다.

예제: 비교 타입 명시적으로 설정

#include <iostream>
#include <compare>

struct Data {
    int x;

    std::strong_ordering operator<=>(const Data&) const = default;
};

struct User {
    std::string name;
    int score;

    std::weak_ordering operator<=>(const User& other) const {
        return score <=> other.score;
    }
};

int main() {
    Data d1{10}, d2{20};
    User u1{"Alice", 90}, u2{"Bob", 90};

    if (d1 < d2) std::cout << "d1이 d2보다 작음 (강한 순서)\n";
    if (u1 == u2) std::cout << "u1과 u2는 같은 점수 (약한 순서)\n";
}

출력 결과:

d1이 d2보다 작음 (강한 순서)
u1과 u2는 같은 점수 (약한 순서)
  • Data 구조체는 std::strong_ordering 을 사용하여 완전한 비교가 가능합니다.
  • User 구조체는 std::weak_ordering 을 사용하여 점수만 비교하고 이름은 무시합니다.

요약

  1. 강한 순서 (strong ordering): 모든 비교 연산이 항상 유효함. (ex. 정수, 문자열 비교)
  2. 약한 순서 (weak ordering): 동등한 값이 내부적으로 다를 수 있음. (ex. 나이가 같은 사람)
  3. 부분 순서 (partial ordering): 일부 값에 대해 비교가 불가능할 수도 있음. (ex. 부동소수점 NaN 비교)

C++20의 spaceship operator는 비교 연산을 자동 생성할 뿐만 아니라, 비교 순서의 성격까지 명확하게 지정할 수 있도록 지원합니다. 이를 활용하면 더 안전하고 직관적인 비교 연산을 구현할 수 있습니다.

다음 섹션에서는 사용자 정의 클래스에서 spaceship operator를 적용하는 방법을 살펴보겠습니다.

사용자 정의 클래스에서 spaceship operator 적용하기

C++20의 spaceship operator(<=>)는 기본 타입뿐만 아니라 사용자 정의 클래스에도 쉽게 적용할 수 있습니다. 이를 통해 객체 간의 비교 연산을 효율적으로 처리할 수 있으며, 직접 비교 연산자를 구현하는 번거로움을 줄일 수 있습니다. 이 섹션에서는 사용자 정의 클래스에 spaceship operator를 적용하는 방법을 다룹니다.


기본 구조체에 spaceship operator 적용하기


먼저, 기본적인 사용자 정의 구조체에 spaceship operator를 적용하여 비교 연산을 자동으로 생성하는 방법을 설명합니다.

#include <iostream>
#include <compare>

struct Point {
    int x, y;

    // spaceship operator를 사용하여 자동으로 비교 연산자 생성
    auto operator<=>(const Point&) const = default;
};

int main() {
    Point p1{3, 4}, p2{3, 5}, p3{3, 4};

    std::cout << std::boolalpha;
    std::cout << "p1 == p2: " << (p1 == p2) << "\n"; // 자동 생성된 == 연산자
    std::cout << "p1 != p3: " << (p1 != p3) << "\n"; // 자동 생성된 != 연산자
    std::cout << "p1 < p2: " << (p1 < p2) << "\n";   // 자동 생성된 < 연산자
}

출력 결과:

p1 == p2: false
p1 != p3: false
p1 < p2: true
  • auto operator<=> = default; 를 사용하면 ==, !=, <, >, <=, >= 연산자가 자동으로 생성됩니다.
  • 위 예제에서는 Point 구조체를 정의하고, operator<=>default 로 지정하여 모든 비교 연산자가 자동 생성됩니다.

사용자 정의 비교 기준 적용하기


때로는 비교 연산자가 객체의 특정 멤버 변수나 속성을 기준으로 정의되어야 할 경우가 있습니다. 이런 경우, spaceship operator를 커스터마이즈하여 원하는 방식으로 비교를 할 수 있습니다.

#include <iostream>
#include <compare>

struct Rectangle {
    int width, height;

    // 비례 비교 기준으로 높이와 너비를 고려
    auto operator<=>(const Rectangle& other) const {
        return (width * height) <=> (other.width * other.height);  // 면적 비교
    }
};

int main() {
    Rectangle r1{10, 20}, r2{15, 15}, r3{10, 20};

    std::cout << std::boolalpha;
    std::cout << "r1 == r2: " << (r1 == r2) << "\n";  // 면적 비교
    std::cout << "r1 < r2: " << (r1 < r2) << "\n";    // 면적 비교
}

출력 결과:

r1 == r2: false
r1 < r2: true
  • Rectangle 구조체에서는 면적을 기준으로 두 사각형을 비교합니다.
  • operator<=>를 커스터마이즈하여 width * height 를 비교 기준으로 사용합니다.

부분 순서 비교 적용하기


부분 순서 비교는 비교가 불가능할 수도 있는 값이 있을 때 사용됩니다. 예를 들어, NaN이나 다른 정의되지 않은 상태를 처리해야 할 경우 부분 순서를 적용할 수 있습니다. 이를 사용자 정의 클래스에 적용하는 방법을 보여줍니다.

#include <iostream>
#include <compare>
#include <cmath>

struct Temperature {
    double value;

    // NaN을 고려한 부분 순서 비교
    std::partial_ordering operator<=>(const Temperature& other) const {
        if (std::isnan(value) || std::isnan(other.value))
            return std::partial_ordering::unordered;  // NaN 비교 불가
        return value <=> other.value;  // 정상적인 비교
    }
};

int main() {
    Temperature t1{36.6}, t2{37.0}, t3{NAN};

    std::cout << std::boolalpha;
    std::cout << "t1 == t2: " << (t1 == t2) << "\n";  // 비교 가능
    std::cout << "t1 < t2: " << (t1 < t2) << "\n";    // 비교 가능
    std::cout << "t1 == t3: " << (t1 == t3) << "\n";  // NaN 비교 불가
}

출력 결과:

t1 == t2: false
t1 < t2: true
t1 == t3: false
  • Temperature 구조체에서 NaN 값을 비교할 때는 std::partial_ordering::unordered 를 반환하여 비교 불가능을 처리합니다.

객체 포인터 비교


포인터를 비교하는 경우에도 spaceship operator를 적용할 수 있습니다. 포인터의 비교는 기본적으로 주소 값을 기준으로 비교됩니다.

#include <iostream>
#include <compare>

struct Point {
    int x, y;

    auto operator<=>(const Point&) const = default; // spaceship operator 적용
};

int main() {
    Point p1{1, 2}, p2{3, 4};
    Point* ptr1 = &p1;
    Point* ptr2 = &p2;

    if (ptr1 < ptr2) std::cout << "ptr1이 ptr2보다 작음 (주소 비교)\n";
    if (ptr1 == ptr2) std::cout << "ptr1과 ptr2는 같은 주소\n";
}

출력 결과:

ptr1이 ptr2보다 작음 (주소 비교)
  • 포인터의 경우 주소 값을 기준으로 비교되며, 객체 자체의 값이 아닌 메모리 주소가 비교 대상이 됩니다.

요약

  1. 기본 비교 연산자: spaceship operator를 사용하여 자동으로 ==, !=, <, >, <=, >= 연산자를 생성할 수 있습니다.
  2. 비교 기준 커스터마이즈: 비교 연산자가 특정 멤버 변수나 계산된 값을 기준으로 동작하도록 정의할 수 있습니다.
  3. 부분 순서: NaN과 같은 비교가 불가능한 값을 처리하기 위해 std::partial_ordering을 사용할 수 있습니다.
  4. 포인터 비교: 포인터를 비교할 때는 주소 값 기준으로 비교가 수행됩니다.

사용자 정의 클래스에 spaceship operator를 적용하면 코드가 간결해지고, 여러 비교 연산자를 효율적으로 처리할 수 있습니다.

spaceship operator와 std::sort 활용

C++20의 spaceship operator(<=>)를 활용하면 객체 비교 연산이 간소화될 뿐만 아니라, 표준 알고리즘(std::sort 등)과의 연동도 쉬워집니다. 기존에는 std::sort 를 사용할 때 operator< 를 반드시 정의해야 했지만, spaceship operator를 사용하면 한 줄의 코드만으로 정렬이 가능해집니다.


std::sort와 spaceship operator 사용하기

아래 예제에서는 spaceship operator를 사용하여 std::sort 가 사용자 정의 객체를 올바르게 정렬하는지 확인합니다.

#include <iostream>
#include <vector>
#include <algorithm>
#include <compare>

struct Point {
    int x, y;

    // spaceship operator 적용
    auto operator<=>(const Point&) const = default;
};

int main() {
    std::vector<Point> points = {{3, 4}, {1, 2}, {5, 1}, {2, 3}};

    // std::sort는 기본적으로 operator< 를 사용하므로 <=> 연산자가 있으면 자동 적용됨
    std::sort(points.begin(), points.end());

    // 정렬된 결과 출력
    for (const auto& p : points) {
        std::cout << "(" << p.x << ", " << p.y << ")\n";
    }
}

출력 결과:

(1, 2)
(2, 3)
(3, 4)
(5, 1)
  • std::sort()는 기본적으로 operator< 를 요구하지만, operator<=> 가 정의된 경우 자동으로 이를 사용하여 정렬합니다.
  • auto operator<=> = default; 를 선언하면 operator< 가 자동 생성되어 std::sort 와 자연스럽게 연동됩니다.

사용자 지정 정렬 기준 적용하기

기본 정렬 기준이 아닌 사용자 지정 기준으로 정렬하고 싶다면, std::sort람다 함수 또는 명시적인 비교 연산자 함수를 전달할 수 있습니다.

1. 람다 함수 활용

#include <iostream>
#include <vector>
#include <algorithm>
#include <compare>

struct Point {
    int x, y;

    // spaceship operator 적용
    auto operator<=>(const Point&) const = default;
};

int main() {
    std::vector<Point> points = {{3, 4}, {1, 2}, {5, 1}, {2, 3}};

    // y 좌표 기준 정렬 (람다 함수 사용)
    std::sort(points.begin(), points.end(), [](const Point& a, const Point& b) {
        return a.y < b.y;
    });

    // 정렬된 결과 출력
    for (const auto& p : points) {
        std::cout << "(" << p.x << ", " << p.y << ")\n";
    }
}

출력 결과:

(5, 1)
(1, 2)
(2, 3)
(3, 4)
  • 기본적으로 std::sort<=> 연산자를 사용하여 정렬하지만, 특정 기준(예: y 값 기준 정렬)이 필요할 때 람다 함수를 활용할 수 있습니다.

2. 사용자 지정 비교 연산자 제공

#include <iostream>
#include <vector>
#include <algorithm>
#include <compare>

struct Point {
    int x, y;

    // spaceship operator 적용
    auto operator<=>(const Point&) const = default;
};

// 정렬 기준을 사용자 지정하는 비교 함수 객체
struct CompareByY {
    bool operator()(const Point& a, const Point& b) const {
        return a.y < b.y; // y 값 기준으로 정렬
    }
};

int main() {
    std::vector<Point> points = {{3, 4}, {1, 2}, {5, 1}, {2, 3}};

    // 사용자 지정 비교 함수 객체를 이용한 정렬
    std::sort(points.begin(), points.end(), CompareByY());

    // 정렬된 결과 출력
    for (const auto& p : points) {
        std::cout << "(" << p.x << ", " << p.y << ")\n";
    }
}
  • 비교 연산을 함수 객체(CompareByY)로 캡슐화하여 가독성을 높일 수 있습니다.
  • 람다 함수와 비교했을 때 코드의 재사용성이 높아집니다.

정렬 기준이 여러 개일 경우


spaceship operator를 활용하면 여러 개의 기준을 사용하여 정렬할 수도 있습니다.

#include <iostream>
#include <vector>
#include <algorithm>
#include <compare>

struct Point {
    int x, y;

    // spaceship operator 직접 정의 (x를 먼저 비교, 같으면 y 비교)
    std::strong_ordering operator<=>(const Point& other) const {
        if (auto cmp = x <=> other.x; cmp != 0) return cmp;
        return y <=> other.y;
    }
};

int main() {
    std::vector<Point> points = {{3, 4}, {1, 2}, {5, 1}, {2, 3}, {3, 1}, {3, 5}};

    std::sort(points.begin(), points.end());  // x 값 우선 비교, x가 같으면 y 비교

    for (const auto& p : points) {
        std::cout << "(" << p.x << ", " << p.y << ")\n";
    }
}

출력 결과:

(1, 2)
(2, 3)
(3, 1)
(3, 4)
(3, 5)
(5, 1)
  • x 값을 우선 비교하고, x 값이 같으면 y 값을 비교하는 방식으로 정렬됩니다.
  • operator<=> 를 직접 정의하여 두 개의 기준을 한 번에 적용할 수 있습니다.

요약

  1. spaceship operator가 있으면 std::sort 와 자연스럽게 연동됩니다.
  2. 기본적으로 operator<=> 를 정의하면 모든 비교 연산이 자동 생성되므로 std::sort 사용이 간편해집니다.
  3. 사용자 지정 정렬 기준이 필요하면 람다 함수나 비교 연산자 객체를 사용하여 적용할 수 있습니다.
  4. 복합 정렬 기준이 필요한 경우, operator<=> 를 커스터마이즈하여 다중 필드를 비교할 수 있습니다.

C++20의 spaceship operator를 활용하면 코드를 더욱 간결하게 유지하면서도 유연한 정렬 기능을 손쉽게 구현할 수 있습니다.

유용한 예제와 실습 문제

C++20의 spaceship operator는 여러 가지 상황에서 유용하게 사용될 수 있습니다. 이 섹션에서는 실제 코드 예제를 통해 spaceship operator를 어떻게 활용할 수 있는지 보여주고, 실습 문제를 통해 학습 내용을 강화해보겠습니다.


예제 1: 3D 벡터 비교

3D 벡터 객체를 비교하는 문제에서 spaceship operator를 사용하여 코드를 간결하게 만드는 방법을 보여줍니다.

#include <iostream>
#include <compare>

struct Vector3D {
    double x, y, z;

    // spaceship operator를 사용하여 자동 비교 연산자 생성
    auto operator<=>(const Vector3D&) const = default;
};

int main() {
    Vector3D v1{1.0, 2.0, 3.0};
    Vector3D v2{1.0, 2.0, 4.0};

    std::cout << std::boolalpha;
    std::cout << "v1 == v2: " << (v1 == v2) << "\n"; // == 연산자
    std::cout << "v1 < v2: " << (v1 < v2) << "\n";   // < 연산자
    std::cout << "v1 != v2: " << (v1 != v2) << "\n";  // != 연산자
}

출력 결과:

v1 == v2: false
v1 < v2: true
v1 != v2: true
  • Vector3D 구조체에서 auto operator<=> = default; 를 사용하여 ==, !=, <, >, <=, >= 연산자를 자동으로 생성합니다.
  • Vector3D 객체를 비교할 때 각 구성 요소(x, y, z)에 대해 비교가 이루어집니다.

예제 2: 정렬 가능한 학생 객체

학생 객체를 점수에 따라 정렬하는 예제입니다. spaceship operator를 사용하여 학생 객체를 쉽게 정렬할 수 있습니다.

#include <iostream>
#include <vector>
#include <algorithm>
#include <compare>

struct Student {
    std::string name;
    int score;

    // 점수를 기준으로 비교 (score 값으로 우선 정렬)
    auto operator<=>(const Student& other) const = default;
};

int main() {
    std::vector<Student> students = {{"Alice", 90}, {"Bob", 85}, {"Charlie", 95}};

    // 점수 기준으로 정렬
    std::sort(students.begin(), students.end());

    for (const auto& student : students) {
        std::cout << student.name << ": " << student.score << "\n";
    }
}

출력 결과:

Bob: 85
Alice: 90
Charlie: 95
  • Student 객체는 score 값을 기준으로 자동으로 정렬됩니다.
  • operator<=>가 정의되어 있기 때문에 정렬 과정에서 비교 연산자가 자동으로 사용됩니다.

예제 3: 복잡한 객체 비교 (다중 기준)

두 사람을 나이와 이름을 기준으로 비교하는 예제입니다. 먼저 나이를 비교하고, 그 값이 같을 경우 이름을 기준으로 비교합니다.

#include <iostream>
#include <string>
#include <compare>

struct Person {
    std::string name;
    int age;

    // 나이를 우선 비교하고, 나이가 같으면 이름으로 비교
    auto operator<=>(const Person& other) const {
        if (auto cmp = age <=> other.age; cmp != 0) return cmp;
        return name <=> other.name;
    }
};

int main() {
    Person p1{"Alice", 30}, p2{"Bob", 25}, p3{"Alice", 25};

    std::cout << std::boolalpha;
    std::cout << "p1 == p2: " << (p1 == p2) << "\n";
    std::cout << "p1 < p3: " << (p1 < p3) << "\n";
    std::cout << "p2 > p3: " << (p2 > p3) << "\n";
}

출력 결과:

p1 == p2: false
p1 < p3: false
p2 > p3: true
  • Person 구조체에서는 나이가 우선 비교되고, 나이가 같으면 이름을 기준으로 비교됩니다.
  • operator<=>를 커스터마이즈하여 다중 기준의 비교를 효율적으로 처리할 수 있습니다.

실습 문제

이제 여러분이 직접 spaceship operator를 활용하여 문제를 해결해보세요. 아래 문제를 풀어보세요.


문제 1: 좌표 정렬하기


Point 구조체를 정의하고, x, y 좌표를 기준으로 좌표 순으로 정렬하세요. x 좌표가 같으면 y 좌표를 기준으로 정렬하십시오.

#include <iostream>
#include <vector>
#include <algorithm>
#include <compare>

struct Point {
    int x, y;

    // spaceship operator를 사용하여 정렬 가능하게 만들기
    auto operator<=>(const Point&) const = default;
};

int main() {
    std::vector<Point> points = {{3, 4}, {1, 2}, {5, 1}, {2, 3}, {3, 2}};

    // 정렬 코드 작성
    std::sort(points.begin(), points.end());

    // 정렬된 결과 출력
    for (const auto& p : points) {
        std::cout << "(" << p.x << ", " << p.y << ")\n";
    }
}

문제 2: 직원 객체 정렬


Employee 구조체를 정의하고, 급여를 기준으로 내림차순 정렬하세요. 급여가 같으면 이름을 기준으로 오름차순 정렬해야 합니다.

#include <iostream>
#include <vector>
#include <algorithm>
#include <string>
#include <compare>

struct Employee {
    std::string name;
    double salary;

    // 급여 내림차순, 이름 오름차순 정렬을 위한 spaceship operator 작성
    auto operator<=>(const Employee& other) const {
        if (auto cmp = salary <=> other.salary; cmp != 0) return -cmp;  // 급여 내림차순
        return name <=> other.name;  // 이름 오름차순
    }
};

int main() {
    std::vector<Employee> employees = {{"John", 50000}, {"Alice", 60000}, {"Bob", 50000}, {"Charlie", 70000}};

    // 직원들 정렬 코드 작성
    std::sort(employees.begin(), employees.end());

    // 정렬된 결과 출력
    for (const auto& emp : employees) {
        std::cout << emp.name << ": " << emp.salary << "\n";
    }
}

요약

  1. 예제를 통해 spaceship operator의 기본 사용법과 다양한 활용 방안을 살펴보았습니다.
  2. 실습 문제를 통해 직접 spaceship operator를 사용하여 객체 비교 및 정렬을 연습할 수 있습니다.
  3. 문제를 풀면서 다양한 기준으로 객체를 비교하고, 이를 통해 코드의 효율성을 높일 수 있습니다.

C++20의 spaceship operator는 객체 비교와 관련된 작업을 간소화하고, 코드의 가독성을 높이는 데 큰 도움이 됩니다.

요약

C++20의 spaceship operator(<=>) 는 객체 비교 연산을 간결하게 처리하고 자동화하는 강력한 기능을 제공합니다. 기존 C++에서 비교 연산자를 일일이 오버로딩해야 했던 불편함을 해소하며, 코드 가독성과 유지보수성을 크게 향상시킵니다.

핵심 정리

  1. 비교 연산 자동 생성
  • auto operator<=> = default; 를 선언하면 ==, !=, <, >, <=, >= 연산자가 자동으로 생성됩니다.
  • 코드 중복이 줄어들고 유지보수가 간편해집니다.
  1. 강한/약한/부분 순서 비교 지원
  • std::strong_ordering: 모든 비교가 완전한 순서를 가짐 (ex. 정수, 문자열).
  • std::weak_ordering: 내부적으로 다르지만 논리적으로 같은 경우 가능 (ex. 이름이 다른 동일 연령의 사람).
  • std::partial_ordering: 일부 값이 비교 불가능할 수도 있음 (ex. 부동소수점 NaN).
  1. 사용자 정의 클래스에서 비교 연산 간소화
  • operator<=> 를 사용하여 사용자 정의 객체를 쉽게 비교할 수 있음.
  • 다중 필드 비교가 가능하며, 원하는 기준을 설정할 수 있음.
  1. 표준 알고리즘(std::sort)과의 연동
  • std::sort() 와 같은 알고리즘이 spaceship operator를 자동으로 활용하여 객체를 정렬할 수 있음.
  • 사용자 지정 정렬 기준을 람다 함수 또는 비교 연산자 객체로 적용 가능.
  1. 실습을 통해 학습 강화
  • Point, Employee, Student 등의 예제를 통해 spaceship operator를 실제 코드에서 어떻게 활용하는지 배움.
  • 실습 문제를 통해 직접 적용하고 테스트할 수 있음.

결론

C++20의 spaceship operator는 비교 연산을 단순화하면서도 강력한 기능을 제공합니다.
이를 활용하면 객체 비교를 더욱 효율적으로 수행할 수 있으며, 코드의 유지보수성을 높이고 표준 라이브러리와 쉽게 연동할 수 있습니다.
C++을 활용한 소프트웨어 개발에서 spaceship operator를 적극적으로 사용하여 더 깔끔하고 직관적인 코드를 작성해보세요! 🚀

목차
  1. spaceship operator란?
    1. spaceship operator의 기본 문법
    2. spaceship operator의 반환값
  2. 기존 비교 연산자의 한계
    1. 모든 비교 연산자를 직접 구현해야 하는 문제
    2. 유지보수의 어려움
  3. spaceship operator의 구현 방식
    1. 기본적인 spaceship operator 구현
    2. 수동으로 spaceship operator 정의하기
    3. 기본 제공되는 비교 타입
    4. spaceship operator 도입의 장점
  4. 비교 연산 자동 생성의 원리
    1. 자동 생성되는 비교 연산자
    2. 예제: 기본적인 자동 생성 원리
    3. 연산자 자동 생성 규칙
    4. 예제: operator<=> 없이 operator== 만 정의할 경우
    5. 비교 연산 자동 생성의 장점
    6. 요약
  5. 강한, 약한, 부분 순서 비교
    1. 비교 순서의 개념
    2. 1. 강한 순서 비교 (`std::strong_ordering`)
    3. 2. 약한 순서 비교 (`std::weak_ordering`)
    4. 3. 부분 순서 비교 (`std::partial_ordering`)
    5. 비교 순서 정리
    6. spaceship operator와 비교 타입의 조합
    7. 예제: 비교 타입 명시적으로 설정
    8. 요약
  6. 사용자 정의 클래스에서 spaceship operator 적용하기
    1. 기본 구조체에 spaceship operator 적용하기
    2. 사용자 정의 비교 기준 적용하기
    3. 부분 순서 비교 적용하기
    4. 객체 포인터 비교
    5. 요약
  7. spaceship operator와 std::sort 활용
    1. std::sort와 spaceship operator 사용하기
    2. 사용자 지정 정렬 기준 적용하기
    3. 1. 람다 함수 활용
    4. 2. 사용자 지정 비교 연산자 제공
    5. 정렬 기준이 여러 개일 경우
    6. 요약
  8. 유용한 예제와 실습 문제
    1. 예제 1: 3D 벡터 비교
    2. 예제 2: 정렬 가능한 학생 객체
    3. 예제 3: 복잡한 객체 비교 (다중 기준)
    4. 실습 문제
    5. 요약
  9. 요약
    1. 핵심 정리
    2. 결론