[Effective C++] #define을 쓰려거든 const, enum, inline을 떠올리자
가급적 선행 처리자보다 컴파일러를 더 가까이 하자.
#define의 문제
#define은 C++ 언어 자체의 일부가 아닌 것으로 취급될 수 있다.
#define ASPECT_RATIO 1.653
ASPECT_RATIO가 기호식 이름(symbolic name)으로 보이지만 컴파일러에서는 보이지 않는다. 소스 코드가 컴파일러에게 넘어가기 전에 선행 처리자가 밀어버리고 숫자 상수로 바꾸어 버리기 때문이다.
대체된 코드에서 컴파일 에러가 발생하게 되면 소스 코드에는 ASPECT_RATIO가 있는데 에러 메시지에는 1.653이라는 숫자가 생기게 된다. 만약 다른 사람이 작성한 것이라면 이것을 찾는데 시간을 허비해야 되기 때문이다. 또한 기호식 디버거(symbolic debugger)에서도 기호 테이블에 이름이 들어가지 않기 때문에 문제가 나타날 소지가 있다.
이 문제 해결법은 매크로 대신 상수를 쓰는 것이다.
const double AspectRatio = 1.653;
// 대문자로만 효기하는 이름은 대개 매크로에 쓰이는 것이여서,
// 이름 표기도 바꾼다.
AspectRatio 는 언어 차원에서 지원하는 상수 타입의 데이터이기 때문에 컴파일러의 눈에도 보이며 기호 테이블에도 들어간다. 상수가 부동소수점 실수 타입일 경우 컴파일을 거친 최종 코드의 크기가 #define을 썼을 때보다 작게 나올 수 있다.
이유는 매크로를 쓰면 코드에 ASPECT_RATIO가 등장시 모두 1.653으로 바뀌면서 여러개의 사본이 생기지만, 상수 타입의 AspectRatio는 여러번 쓰더라도 딱 하나의 사본이 생기기 때문이다.
#define을 상수로 교체할때 조심해야할 두가지의 경우
첫 번째, 상수 포인터(constant pointer)를 정의하는 경우
const char * const authorName = "Scott Meyers"
보다는 아래와 같이 정의하는 것이 더 좋다.
const std::string authorName("Scott Meyers");
두 번째, 클래스 멤버로 상수를 정의하는 경우, 즉 클래스 상수를 정의하는 경우.
class GamePlayer {
private :
static const int NumTurns = 5; // 상수 선언
int scores[NumTurns]; // 상수를 사용하는 부분
};
위의 NumTurns는 정의가 아니라 선언(declaration)된 것이다. C++에서 사용하고자 하는 것에 대해 보통 정의가 되어야 하지만, 정적 멤버로 만들어지는 정수류(각종 정수 타입, char, bool 등) 타입의 클래스 내부 상수는 예외이다.
const int GamePlayer::NumTurns; // NumTurns의 정의
클래스 상수의 정의는 구현 파일에 위치하는데, 클래스 상수의 초기값은 해당 상수가 선언된 시점에서 바로 주어지기 때문이다. #define으로 클래스 상수를 만들어 정의하는 데 쓸 수도 없을 뿐 아니라 어떠한 형태의 캡슐화 혜택도 받을 수 없다. 즉 private 성격의 #define은 없다는 것을 이야기 한다.
// 오래된 컴파일러에서 인식하게 하는 방법
const int GamePlayer::NumTurns= 5;
GamePlayer::scores와 같이 클래스를 컴파일 하는 도중에 클래스 상수의 값이 필요할 때 문제가 발생하게 된다. 이 경우 enum을 사용하는 것을 추천한다.
class GamePlayer {
private :
// NumTurns를 5에 대한 기호식 이름으로 만든다.
enum { NumTurns = 5 };
int scores[NumTurns]; // 상수를 사용하는 부분
};
이 방법을 사용하는 이유는 동작 방식이 const보다는 #define에 더 가까워 enum의 주소를 알 수 없다. 그리고 const 객체에 대한 어떤 형태의 쓸데 없는 메모리 할당도 일어나지 않는다. 또한 상당히 많은 코드에서 사용되고 있으며, 템플릿 메타프로그래밍의 핵심 기법이기도 하다.
#define 지시자의 또 다른 오용 사례는 매크로 함수이다.
#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가 한 번 증가한다.
인라인 함수에 대한 템플릿을 사용하는 것으로 해결이 가능하다.
template<typename T>
inline void callWithMax(const T& a, const T& b)
{
f(a > b ? a : b);
}
위와 같이 사용하면 매크로의 효율과 동작방식 및 타입의 안전성을 확보 가능하다. 그리고 ()로 하니씩 씌워주지 않아도 되며, 인자를 여러번 평가할지도 모른다는 걱정이 없어진다.
결론
const, enum, inline을 사용하면 선행 처리자 #define를 꼭 써야 하는 경우가 많이 줄어 들게 된다.
단순한 상수를 쓸 때는, #define보다 const 객체 혹은 enum을 우선 생각한다.
함수처럼 쓰이는 매크로를 만들려면, #define 매크로보다 인라인 함수를 우선 생각한다.
참고 : Effective C++