-
[Effective C++] 02. 선행 처리자보다 컴파일러를 더 가까이 하자C++/Effective C++ 2025. 11. 5. 17:31
원제목 : Prefer consts, enums, and inlines to #defines (#define을 쓰려거든 const, enum, inlne을 떠올리자)
선행 처리자는 #include ,#define, #ifdef/#ifndef 등의 선언문을 의미합니다.
이번 글에서 살펴볼 선행 처리자는 이 중 #define 입니다.
#define으로 선언된 코드는 컴파일 시 전처리 과정에서 코드를 지정한 값으로 교체합니다.
따라서 디버깅 과정에서 해당 심볼은 발견할 수 없으며, 심볼의 사용 횟수에 비례하여 목적 코드의 크기 또한 증가합니다.
이번 글에서는 #define을 대체할 수 있는 기법들을 살펴보도록 하겠습니다.
1. 상수로 사용되는 #define
더보기#define을 상수를 정의하는 데 사용할 경우 다음과 같이 사용할 수 있으며
이는 const로 교체할 수 있습니다.
// Preprocess #define PI 3.141592 // const value const double Pi = 3.141592; double foo = 2 * PI; double bar = 3 * PI;이렇게 const를 이용한 상수를 사용하는 것 만으로도 서론에서 언급한 심볼이 가려지는 것, 목적 코드가 커지는 것을 방지할 수 있습니다.
하지만 상수를 사용할 때 주의할 점이 몇 가지 있습니다.
첫 번째는 문자열 리터럴 입니다.
#define MY_STR "Hello world!" // 1. const const const char* const myString = "Hello world!"; // 2. const string const string myString("Hello world!");문자열 리터럴을 char*로 사용할 경우, const를 두 번 사용해야 합니다.
혹은, STL의 string 컨테이너를 사용할 수 있습니다.
두 번째는 클래스 멤버 상수입니다.
이 경우 상수이기 때문에 static 멤버로 사용하는 것이 좋습니다.
clss Widget{ private: static const double Pi; ... }; const double Widget::Pi = 3.141592;실수 타입은 선언 시 값을 초기화 할 수 없습니다. (일부 구버전 컴파일러는 정수 또한 불가합니다.)
따라서 클래스 정의 밖에서 값을 정의해야 합니다.
2. 상수로 사용되는 #define : C++11 이후
더보기이전 문단에서 상수에 대해 #define 대신 const를 사용하는 것, 그리고 주의점을 살펴보았습니다.
우선, #define을 const로 교체하면서 얻을 수 있는 이점은 디버깅에 용이하다는 것, 추가적으로 타입 안정성을 확보할 수 있다는 것 입니다.
하지만 클래스 정적 멤버로 사용할 수 없는 등, #define의 컴파일 시점의 상수 역할은 수행할 수 없는 경우도 있습니다.
이는 C++11의 constexpr로 해결할 수 있습니다.
constexpr은 컴파일 시점에 평가가 이루어지는 값에 사용되는 예약어 입니다.
clss Widget{ private: static constexpr double Pi = 3.141592; ... };이를 사용하면 클래스 정적 멤버를 선언할 때 값을 지정할 수 있습니다.
3. 함수로 사용되는 #define
더보기#define을 함수로 사용할 경우 다음과 같이 사용할 수 있으며
이는 inline으로 교체할 수 있습니다.
// Preprocess #define CALL_WITH_MAX(a, b) f((a) > (b) ? (a) : (b)) // const value template<typename T> inline void callWithMax(const T& a, const T& b){ f(a > b ? a : b); }위 매크로와 inline함수는 전달받은 a와 b중 더 큰 값을 사용해 함수 f를 호출합니다.
이러한 함수의 교체는 상수 사용시 얻는 타입 안정성 등의 문제 뿐만 아니라 다음의 문제 또한 해결합니다.
int a = 5, b = 0; CALL_WITH_MAX(++a, b); // a가 두 번 증가 CALL_WITH_MAX(++a, b+10); // a가 한 번 증가 // 매크로 대입 f((++a) > (b) ? (++a) : (b));매크로가 단순히 코드를 교체해주기 때문에, 위와 같이 표현식이 중복 평가되는 일이 발생할 수 있습니다.
함수를 이용할 경우 표현식이 한 번만 평가되어 이러한 문제를 해결할 수 있습니다.
4. 함수로 사용하는 #define : C++11 이후
더보기기존 코드의 inline함수는 상수의 const와 마찬가지로 컴파일 타임에 평가가 불가능하다는 단점이 있습니다.
// 가능 #define SQUARE(x) ((x) * (x)) int arr[SQUARE(5)]; // 불가능 inline int square(int x) { return x * x; } int arr[square(5)];inline이 함수의 본문을 대체하는 예약어이기 때문입니다.
이 또한 C++11 이후 constexpr을 통해 개선할 수 있습니다.
// 가능 constexpr int square(int x) { return x * x; } int arr[square(5)];constexpr로 선언한 함수는 컴파일 시점에 평가가 이루어집니다.
따라서 #define이 수행하던 역할인 컴파일 시점의 평가가 가능합니다.
상수를 지정하여 재사용성과 가독성을 늘리는 것은 좋은 코딩 습관입니다.
하지만 사용하기에 따라 크고 작은 문제가 발생할 수 있기 때문에, 이를 안전하게 대체할 수 있는 방법을 알아두는 것이 좋겠습니다.
이번 글이 도움이 되셨기를 바랍니다.
감사합니다.
'C++ > Effective C++' 카테고리의 다른 글
[Effective C++] 06. 특수 멤버 함수의 자동 생성에 관하여 (2) (0) 2025.12.02 [Effective C++] 05. 특수 멤버 함수의 자동 생성에 관하여 (0) 2025.12.01 [Effective C++] 04. 객체를 사용하기 전에 반드시 초기화하자 (0) 2025.11.27 [Effective C++] 03. const를 자주 사용하자 (0) 2025.11.13 [Effective C++] 01. C++는 언어들의 연합체이다 (0) 2025.11.04