C++23에서는 컴파일러에게 특정 코드 경로가 절대 도달하지 않을 것임을 알리는 새로운 함수인 std::unreachable()가 도입되었습니다. 이번 글에서는 std::unreachable()의 개념과 사용법, 그리고 이전 버전과 비교하여 어떻게 개선되었는지 알아보겠습니다.
std::unreachable()란 무엇인가요?
std::unreachable()는 코드 상에서 절대 실행될 수 없는 경로임을 컴파일러에 알려주는 함수입니다. 이는 정의되지 않은 동작(Undefined Behavior)을 발생시키며, 컴파일러가 이 정보를 활용하여 해당 코드 경로를 제거하거나 최적화할 수 있게 합니다. 예를 들어, switch 문의 default 분기가 절대 발생하지 않는 경우나 특정 조건이 항상 참이라 이외의 경로로는 진행되지 않을 때, std::unreachable()를 통해 해당 경로가 비현실적임을 명시할 수 있습니다.
이전 버전에서는 어떻게 했나요?
C++23 이전에는 도달 불가능한 경로를 표시하기 위해 다음과 같은 우회적인 방법을 사용했습니다.
1. assert(false) 또는 throw 사용
switch (value) {
case 1: /* ... */ break;
case 2: /* ... */ break;
default:
// 여기에 도달하지 않는다고 가정하지만, 컴파일러는 확신할 수 없음
assert(false);
// 또는
// throw std::runtime_error("unreachable");
}
- 문제점:
- assert(false)는 런타임 검사이며, 최적화에 직접 기여하지 않습니다.
- 예외를 던지는 것은 런타임 비용이 발생하며, 컴파일러 최적화를 위해 "절대 도달하지 않는다"는 의미를 충분히 전달하지 못합니다.
- 컴파일러별 확장(예: GCC의 __builtin_unreachable(), MSVC의 _assume(false))를 사용할 수도 있었지만, 이는 이식성 문제를 낳습니다.
2. 컴파일러별 확장 함수 사용
#ifdef __GNUC__
#define UNREACHABLE() __builtin_unreachable()
#elif defined(_MSC_VER)
#define UNREACHABLE() __assume(0)
#else
#define UNREACHABLE() do { assert(false); } while(0)
#endif
- 문제점:
- 비표준적인 방법으로, 이식성과 유지보수성 저하
- 코드 가독성 및 명확성 저하
C++23의 std::unreachable()를 사용한 개선
이제 표준 라이브러리에서 제공하는 std::unreachable()를 사용할 수 있으므로, 컴파일러별 조건부 코드나 비표준 함수에 의존할 필요가 없습니다.
예제: std::unreachable() 사용
#include <utility> // std::unreachable
#include <iostream>
int processValue(int value) {
switch (value) {
case 1: return 10;
case 2: return 20;
default:
std::unreachable();
// 이 지점은 절대 도달하지 않는다고 가정
// 컴파일러는 여기서 UB 발생 가능성을 인지하여 최적화
}
}
int main() {
std::cout << processValue(1) << '\n'; // 10 출력
// processValue(99)는 UB 발생 (실제로는 호출하지 않는 상황 가정)
return 0;
}
- std::unreachable()는 이 부분이 절대 실행되지 않음을 컴파일러에게 알리고, 정의되지 않은 동작을 발생시킵니다.
- 컴파일러는 이 정보를 활용하여 불필요한 분기 제거 등 최적화를 수행할 수 있습니다.
어떻게 좋아졌나요?
- 표준화된 방법: 이전에는 컴파일러별 확장 함수에 의존했지만, 이제 표준 함수 사용으로 이식성과 일관성 확보
- 직관적 코드: std::unreachable()를 보면 이 코드 경로가 도달 불가능함을 명확히 알 수 있어 가독성과 유지보수성 향상
- 최적화 기회 제공: 컴파일러가 더욱 강력한 최적화를 적용할 수 있으므로, 성능 개선에 도움이 될 수 있습니다.
상세한 예제와 비교
1. 절대 실행되지 않는 함수에 표시
int getImpossibleValue() {
// 어떤 이유로 여기가 절대 호출되지 않는다는 확신이 있음
std::unreachable();
// 이후 코드는 UB, 삭제해도 됨
}
- 이전에는 assert(false)나 예외 던지기 등을 했지만, 이제는 명확히 이 경로가 불가능함을 표시
2. 코드 정리 및 최적화
int compute(int x) {
if (x > 0) {
return x * 2;
} else if (x == 0) {
return 0;
} else {
std::unreachable();
}
}
- x가 음수일 수 없는 상황을 가정하고, 이를 std::unreachable()로 명확히 표현하면, 컴파일러는 음수에 대한 분기 제거 등 최적화 가능
주의 사항
- 정의되지 않은 동작: std::unreachable()는 해당 위치에 실행이 도달하면 UB를 발생시킵니다. 개발자가 절대 도달하지 않는다는 보장을 실제로 해야 합니다.
- 디버깅 난이도: 잘못된 가정으로 std::unreachable()를 사용하는 경우 디버깅이 어려울 수 있습니다. 조건이 항상 만족된다는 확신이 있는 경우에만 사용하세요.
- 컴파일러 지원: C++23 기능이므로, 이를 지원하는 컴파일러와 표준 라이브러리가 필요합니다.
요약
C++23의 std::unreachable()는 코드 상에서 결코 도달할 수 없는 경로임을 표준화된 방식으로 명시하는 기능을 제공합니다. 이전에는 컴파일러별 확장이나 우회적인 방법에 의존했지만, 이제는 표준 라이브러리 함수 사용으로 이식성, 가독성, 유지보수성, 최적화를 모두 개선할 수 있습니다. 단, 이 함수 호출 부위가 정말 도달 불가능하다는 보장이 필요하다는 점에 유의해야 합니다.
참고 자료:
'개발 이야기 > C++' 카테고리의 다른 글
[C++23 새기능 소개] std::chrono 라이브러리 확장 (0) | 2024.12.08 |
---|---|
[C++23 새기능 소개] std::optional의 모나딕 연산(transform, transform_or, and_then, or_else) (0) | 2024.12.08 |
[C++23 새기능 소개] std::ranges::to 함수 템플릿 (0) | 2024.12.08 |
[C++23 새기능 소개] [[assume]] 속성 (0) | 2024.12.08 |
[C++23 새기능 소개] std::flat_map과 std::flat_set (0) | 2024.12.08 |