- 상수를 쓸 때는, #define보다는 const, enum을 우선으로
- 함수처럼 쓰이는 매크로는 #define 매크로보다 inline함수를 우선으로
#define 대신 const, enum, inline
#define ASPECT_RATIO 1.653이라고 상수를 정의했다고 치자.
우리에게는 ASPECT_RATIO라는 기호식 이름(symbolic name)으로 보이지만 컴파일러는 전혀 알 수 없다.
소스 코드가 컴파일러에게 넘어가기 전에 전처리 단계에서 숫자 상수로 바꾸어버린다.
그 결과, ASPECT_RATIO는 기호 테이블에 들어가지 않고 1.653이라는 숫자로 들어가게 된다.
따라서, 우리는 에러를 보면 ASPECT_RATIO는 이름은 찾아볼 수 없다.
이러한 문제를 매크로대신 상수(const)를 사용하여 해결할 수 있다.
const double AspectRatio = 1.653;
이렇게 사용하면 언어 차원에서 지원하는 상수 타입의 데이터이기 때문에 컴파일러도 알 수 있고 기호 테이블에도 들어간다. (부동소수점 실수 타입일 경우에는 컴파일을 거친 최종 코드의 크기가 #define을 사용했을 때보다 작게 나올 수 있다.)
#define → const 주의점
#define을 상수로 교체하려 할 때, 주의할 부분이 2가지 있다.
- 상수 포인터를 정의하는 경우
상수 정의는 대개 헤더 파일에 작성하기 때문에 포인터는 꼭 const로 선언해 주어야 하고, 포인터가 가리키는 대상까지 const로 선언하는 것이 보통이다.
const char * const authorName = "Scott Meyers";
const std::string autorName("Scott Meyers");
- 클래스 멤버로 상수를 정의하는 경우
클래스 상수를 #define으로 만드는 것은 애초에 말이 되지 않는다, 왜냐하면 #define은 유효범위가 존재하지 않는다.
어떤 상수의 유효범위를 클래스로 한정하고자 할 때 그 상수를 멤버로 만들어야 하는데, 그 상수의 사본 개수가 한 개를 넘지 못하게 하고 싶다면 정적(static) 멤버로 만들어야 한다.
class GamePlayer{
private:
static const int NumTurns = 5; // 상수 선언
int scores[NumTurns]; // 상수 사용
...
};
여기서 NumTurns는 '선언(declaration)'된 것이다. 사용하고자 하는 것에 대해 정의가 되어야 하지만 정적 멤버로 만들어지는 정수류 타입의 클래스 내부 상수는 예외이다. (주소를 취하지 않는 한, 선언만 해도 문제가 없다, 컴파일러에 따라 정의해야 될 수도)
const int GamePlayer::NumTurns; //NumTurns의 정의(상수지만 값 부여X)
클래스 상수의 정의는 구현 파일에 둔다. 정의에는 상수의 초기값이 있으면 안된다.
(상수의 초기화는 선언된 시점에서 결정)
(오래된 컴파일러는 정적 클래스 멤버가 선언된 시점에 초기값을 주는 것이 맞지 않다고 판단하기 때문에 클래스 내 초기화를 금지하는 컴파일러에서는 정의하는 시점에 값을 주어야 한다)
만약, 클래스를 컴파일하는 도중에 클래스 상수의 값이 필요할 때는 enum hack을 사용하면 된다.
enum hack은 나열자(enimerator)는 int를 대체할 수 있다는 것을 활용한 것이다.
class GamePlayer{
private:
enum { NumTurns = 5 }; //enum hack: NumTurns를 5에 대한 기호식으로
int scores[NumTurns]; //ok
...
};
enum hack은 const보다는 #define에 더 가깝다.
즉, const의 주소를 얻어 내는 것은 가능하지만 enum의 주소를 취하는 것은 불가능하다.
만약 내가 선언한 정수 상수를 다른 사람이 주소를 얻거나 참조자를 사용하는 일을 피하고 싶다면 enum hack을 사용하면 좋다. (+ enum은 메모리도 사용하지 않는다.)
#define 함수 매크로 대신 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가 한 번 증가
a가 사용된 횟수만큼 ++를 평가하기 때문에 결과가 달라진다.
이러한 문제를 inline 함수로 해결할 수 있다.
template<typename T>
inline void callWithMax(const T& a, const T& b)
{
f(a > b ? a : b);
}