#define의 문제점
#define은 전처리 지시문으로, 컴파일러가 코드를 읽기 전에 텍스트 치환이 이루어집니다.
#define ASPECT_RATIO 1.653
컴파일러는 ASPECT_RATIO라는 이름을 알 수 없고, 단지 1.653만을 인식합니다. 그 결과 ASPECT_RATIO라는 이름은 컴파일러가 쓰는 기호 테이블에도 들어가지 않습니다.
이로 인해 에러 메시지엔 1.653만 보여 코드 분석과 디버깅이 어려워집니다.
대안 1 : const
const double AspectRatio = 1.653;
AspectRatio는 상수 타입의 데이터이므로 컴파일러의 눈에도 보이며 기호 테이블에도 들어갑니다.
상수로 교체하려는 경우 조심해야 할 두 가지 경우
1. 상수 포인터(Constant Pointer)의 정의
포인터를 상수로 정의할 때, 포인터 자체를 상수로 만들 것인지, 포인터가 가리키는 값을 상수로 만들 것인지 헷갈릴 수 있습니다.
const char* const authorName = "Scott Meyers"; //const를 두 번 사용
개선 방안: C++에서는 char*보다 string을 사용하는 것이 더 안전하고 편리
const std::string authorName = "Scott Meyers";
2. 클래스 내 정적 상수(static const)의 정의
class GamePlayer {
private:
static const int NumTurns = 5; // 정적 상수 선언
int scores[NumTurns]; // 배열 크기에 사용
};
위의 NumTruns는 ‘선언(declaration)’된 것입니다. ‘정의’가 아닙니다.
- 정적 상수는 클래스 내부에서 선언만 하고, 클래스 외부에서 정의해야 합니다.
- 단, 정수형 정적 상수(각종 정수 타입, char, bool 등)는 클래스 내부에서 선언과 동시에 초기화할 수 있으며, 따로 정의할 필요가 없습니다.
단, 클래스 상수의 주소를 구하는 경우에는 다음과 같이 별도의 정의를 제공해야 합니다.
const int GamePlayer::NumTurns; //NumTurns의 정의
클래스 상수의 정의는 헤더 파일이 아닌 구현 파일에 둡니다.
클래스 상수의 초기값은 해당 상수가 선언된 시점에 바로 주어지기 때문에 정의에는 상수의 초기값이 있으면 안 됩니다.
대안 2: enum
예외 상황: 컴파일 타임에 값이 필요한 경우
NumTurns와 같은 정적 상수를 배열 크기와 같이 컴파일 타임에 필요한 경우에는 일부 컴파일러가 클래스 내부에서의 초기화를 허용하지 않을 수 있습니다. 이 문제를 해결하기 위해 enum hack을 사용할 수 있습니다.
class GamePlayer {
private:
enum { NumTurns = 5 }; // NumTurns를 5에 대한 기호식 이름으로 만든다
int scores[NumTurns]; // 배열 크기 정의
};
Enum Hack(나열자 둔갑술)이란 정적 상수 대신 enum을 사용해 정수 값을 정의하는 방법입니다. 열거형(enum)의 값은 컴파일 타임 상수로 취급되며, 정적 상수의 역할을 대신할 수 있습니다.
- 동작 방식은 const보다 #define에 더 가깝습니다. 메모리를 추가로 할당하지 않습니다.
- 템플릿 메타프로그래밍의 핵심 기법입니다.
대안 3: inline
함수처럼 보이는 매크로는 inline 함수로 대체하는 것이 좋습니다.
매크로 함수라고 부르지만 단순히 치환하기만 하므로 실제로 함수는 아닙니다. 단순히 텍스트를 치환하는 방식으로 동작하므로 전달된 인수가 매크로 내부에서 여러 번 평가될 수 있어 문제를 발생합니다.
// a와 b 중 더 큰 것을 f에 넘겨 호출한다
#define CALL_WITH_MAX(a, b) f( (a) > (b) ? (a) : (b) )
int a = 5, b = 0;
CALL_WITH_MAX(++a, b); // a가 두 번 증가함
CALL_WITH_MAX(++a, b+10); // a가 한 번 증가함
첫 번째 호출: f( (++a) > (b) ? (++a) : (b) )
a의 값이 1 증가하고, a는 6이 됩니다. (6 > 0)는 참이므로, 다시 ++a를 실행하면, a가 또 1 증가하여 7이 됩니다.
두 번째 호출: f( (++a) > (b+10) ? (++a) : (b+10) )
비교식에서 (8 > 10)는 거짓이므로, b+10이 평가되므로 a는 한 번만 증가합니다.
비교를 통해 처리한 결과가 어떤 것이냐에 따라 a가 증가하는 횟수가 달라지는 문제점이 있습니다. 매크로 대신 inline 함수를 사용하면, 인수가 정확히 한 번만 평가되므로 이러한 문제를 방지할 수 있습니다.
template<typename T>
inline void callWithMax(const T& a, const T& b) {
f((a > b) ? a : b);
}
중요!
- 단순한 상수를 쓸 때는, #define보다 const 객체 혹은 enum을 우선 생각합시다.
- 함수처럼 쓰이는 매크로를 만들려면, #define 매크로보다 인라인 함수를 우선 생각합시다.
'책 > Effective C++' 카테고리의 다른 글
[Effective C++] 항목 3: 낌새만 보이면 const를 들이대 보자! (0) | 2024.12.17 |
---|---|
[Effective C++] 항목 1: C++를 언어들의 연합체로 바라보는 안목은 필수 (1) | 2024.12.05 |