#define을 쓰려거든 const, enum, inline을 떠올리자
Effective C++ 공부 내용입니다.
상수
첫 번째 오용 사례
#define ASPECT_RATIO 1.653
- 나에게는
ASPECT_RATIO
라는 것이 symbolic name으로 보이지만 컴파일러에겐 그렇지 않다.- 소스 코드가 컴파일 되기 전에 선행 처리자가
ASPECT_RATIO
를 상수로 바꾸기 때문이다.
- 소스 코드가 컴파일 되기 전에 선행 처리자가
- 그리하여
ASPECT_RATIO
라는 symbol이 컴파일러의 Symbol Table에 들어가지 않는다.
- 그래서 오류가 발생한다면 소스 코드에는 ASPECT_RATIO를 사용했지만 오류 코드에는
1.653
이 있어서 당황할 수 있다. - 내가 만든 것이 아니라면 해당 숫자가 어디에서 왔는지도 모르고 시간을 허비할 것이다.
해결법은 메크로 대신 그냥 상수를 사용하는 것이다.
const double AspectRatio = 1.653;
-
const
는 언어 차원에서 지원하는 상수 타입의 데이터이기에 컴퍼일러가 인지하며 Symbol Table에도 들어간다. -
그리고
#define
을 썼을 때 보다 최종 크기가 작아질 수 있다.- 왜냐하면
#define
은ASPECT_RATIO
가 나오는 부분마다1.653
으로 바뀌면서1.653
이 나온 횟수만큼 사본이 만들어지지만, 상수 타입은 아무리 여러 번 쓰더라도 사본이 딱 한 개 생긴다.
- 왜냐하면
#define을 상수로 교체하는 경우 두 가지 조심할 점
1. 상수 포인터를 정의하는 경우
- 포인터가 가르키는 대상까지
const
로 선언해야 한다.const char* const
처럼
1. 클래스 멤버로 상수를 정의하는 경우
- 어떤 상수의 유효 범위를 클래스로 한정하고자 한다면 멤버 변수로 만들어야 하며, 본의 개수가 한 개를 넘지 못하게 하려면 정적으로 만들어야 한다.
class GamePlayer{
private:
static const int Num = 5;
int scores[Num];
...
};
- c++에서는 사용하고자 하는 것에 대해 정의가 마련되어 있어야 하는 게 보통이지만, 정적 멤버로 만들어지는 정수류 타입의 클래스 내부 상수는 예외이다.
- 이들은 주소를 취하지 않는 한, 정의 없이 선언만 해도 아무 문제가 없게 설계되어 있다.
- 다만, 클래스 상수의 주소를 구하던가, 컴파일러가 잘못 만들어진 관계로 정의를 달라고 하는 경우 정의를 만들어야 한다.
const int GamePlayer::Num;
- 이 때 보편적으로 상수 정의는 헤더 파일에 넣지만, 클래스 상수의 정의는 구현 파일에 둔다.
- 정의에는 상수의 초기값이 있으면 안 된다.
- 왜냐면 상수 선언시 초기화를 바로 하기 때문이다.
- 하지만 엤날 컴파일러 같은 경우 위 같은 문법이 받아들이지 않는 경우가 있다.
- 이유는 정적 클래스 멤버가 선언된 시점에 초기값을 주는 것이 말이 안 된다고 판단하기 때문이다.
- 이 경우에는 간단하게 위에서 말한 문법과 다르게 초기값을 정의 시점에 설정하면 된다.
- 하지만 이 방법은 클래스를 컴파일 하는 도중 클래스 상수의 값이 필요할 때 문제가 생긴다.
- enum hack라는 기법으로 해결할 수 있다.
enum hack
-
해당 기법은 TMP에서도 많이 쓰이기에 눈에 익혀 나쁠 것이 없다.
-
원리는
enum
타입의 값은int
가 놓일 곳에도 쓸 수 있기 때문이다.
class GamePlayer{
private:
enum { Num = 5 };
int scores[Num];
...
};
enum
은 주소를 취할 수 없으니, 선언한 정수 상수를 다른 사람이 주소를 얻거나 참조하는 것이이 싫다면 좋은 자물쇠가 된다.- 또
enum
은#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 MaxValue(const T& a, const T& b){
f(a > b ? a : b);
}
단순한 상수를 쓸 때는,
#define
보다const
객체 또는enum
을 생각하자. 함수처럼 사용하는 메크로를 만들 때,#define
메크로보다 인라인 함수를 생각하자.
댓글남기기