과거 C++98/03 시절에는 정적 상수를 정의할 때 매크로를 사용하는 경우가 흔했습니다. 매크로는 전처리 단계에서 단순한 텍스트 치환을 수행하므로, 타입 정보가 없고 디버깅도 까다롭다는 문제가 있었습니다. 또한 범위(scope) 개념이 없어 어디서나 무분별하게 확장되며, 심지어 이름 충돌 문제도 야기할 수 있었습니다.
모던 C++에서는 이러한 문제를 해결하기 위해 constexpr, const 정적 상수, 그리고 enum class를 적극적으로 활용할 수 있습니다. 이를 통해 타입 안전성과 가독성을 향상시키고, 컴파일 시간 상수 평가를 통한 성능 최적화도 기대할 수 있습니다.
관련 참고 자료:
과거: 매크로 상수의 문제점
C-Style 매크로를 통해 상수를 정의하면, 이는 그저 문자열 치환에 불과합니다. 타입 정보가 없어 사용자가 실수로 잘못된 타입으로 사용해도 컴파일러는 이를 체크하지 못하고, 디버깅 시에도 어디서 어떻게 치환되었는지 추적하기 어렵습니다.
#include <iostream>
#define SIZE 100
#define SQUARE(x) (x * x)
int main() {
int arr[SIZE]; // SIZE는 단순히 100으로 치환
std::cout << SQUARE(5) << "\n"; // (5 * 5)로 치환되어 25 출력
// 실수: 매크로로 인한 예기치 않은 평가
std::cout << SQUARE(1+2) << "\n";
// (1+2 * 1+2) 로 치환 -> 1+2 * 1+2 = 1+2*1+2 = 1+2+2 = 5 출력
// 의도한 9와 다름. 괄호 처리 실수 시 이런 문제 발생.
return 0;
}
위 예제에서 SQUARE(1+2)는 (1+2 * 1+2)로 치환되어 연산 우선순위를 잘못 처리하게 됩니다. 매크로는 이런 식으로 작은 실수가 심각한 버그로 이어질 수 있습니다.
현재: constexpr, enum class를 통한 안전한 상수 표현
constexpr를 통한 타입 안전 정적 상수
constexpr 키워드는 변수를 컴파일 시간 상수로 명시할 수 있습니다. 이는 컴파일러가 해당 값을 컴파일 시간에 평가할 수 있음을 의미하며, 타입 정보도 유지됩니다.
#include <iostream>
constexpr int SIZE = 100;
constexpr int square(int x) {
return x * x;
}
int main() {
int arr[SIZE]; // 컴파일 타임에 100으로 평가
std::cout << square(5) << "\n"; // 컴파일 타임에 25로 평가 가능
std::cout << square(1+2) << "\n"; // 9로 정확히 계산
return 0;
}
constexpr를 사용하면 상수가 타입 안전하게 정의되며, 매크로보다 훨씬 직관적이고 디버깅하기 쉽습니다.
enum class로 명확한 열거형 타입 사용
과거 C 스타일 열거형 enum은 전역 네임스페이스에 정수 상수를 방출하고, 범위와 타입 안전성이 부족했습니다. C++11 이후에는 enum class를 통해 열거형을 독립된 타입으로 다루고, 명확한 스코프를 부여할 수 있습니다.
#include <iostream>
enum class Color : int {
Red = 0,
Green = 1,
Blue = 2
};
int main() {
Color c = Color::Red;
// 정수 캐스팅 필요, 명시적이어서 실수 방지
std::cout << static_cast<int>(c) << "\n"; // 0 출력
return 0;
}
enum class를 사용하면 열거형 값이 고유한 타입으로 취급되며, 다른 타입과 혼동될 위험이 줄어듭니다. 이는 코드의 의도를 명확히 전달하고, 타입 안전성을 향상시킵니다.
왜 이런 변화가 필요한가?
- 타입 안전성 확보
매크로 상수 사용 시 타입 정보가 사라지며, 실수나 오타가 런타임 버그로 이어질 수 있습니다. constexpr와 enum class는 타입 시스템을 통해 실수를 사전에 방지합니다. - 가독성과 유지보수성 개선
매크로 상수 대신 constexpr를 사용하면 상수 정의가 코드 맥락 안에서 명확히 드러나며, enum class는 열거형 값을 스코프에 묶어둠으로써 네임스페이스 오염을 줄이고, 읽기 쉬운 코드를 작성할 수 있습니다. - 컴파일 타임 최적화
constexpr는 컴파일 타임에 값이 결정되므로, 런타임 오버헤드를 줄이고 성능 최적화를 이끌어냅니다.
'개발 이야기 > C++' 카테고리의 다른 글
[모던 C++ #5] C-Style 문자열을 넘어: std::string과 std::string_view로! (0) | 2024.12.14 |
---|---|
[모던 C++ #4] 옛날 for 루프를 버리고 range-based for와 C++20 Ranges로! (0) | 2024.12.14 |
[모던 C++ #2] C-Style 배열을 벗어나: std::array, std::vector 그리고 std::span으로! (0) | 2024.12.14 |
[모던 C++ #1] 이제는 버려야 할 레거시: auto_ptr를 내려놓고 unique_ptr로! (0) | 2024.12.14 |
[C++23 새기능 소개] std::ranges::sample (0) | 2024.12.12 |