#define => enum, const, inline
가급적 전처리자 보다 컴파일러를 더 가까이하자.
아래코드를 보면
#define CIRCLE_RATIO 3.1415
우리에겐 CIRCLE_RATIO 가 기호식이름(symbolic name) 으로 보이지만 소스코드가 컴파일러에게 넘어가기
전에 전처리자가 숫자 상수로 바꾸어 버리기 때문에 컴파일러의 Symbol Table 에 들어가지 않는다.
그래서 에러 발생시 메시지엔 CIRCLE_RATIO가 아닌 3.1415가 있어 사용자를 헷갈리게 할 수 가 있다.
이 문제의 해결 방법으로는 Macro 대신 상수를 쓰는 것이다.
const double CircleRatio = 3.1415 ;// 대문자로만 표기하는 이름은 대게 Macro 에서만 사용언어차원에서 지원하는 상수 데이터 값이기 때문에 컴파일러의 눈에도 보이고 Symbol Table에도 들어간다
또한 이유는 Macro 를 쓰면 코드에 매칭되는 모든 곳에 3.1415로 바꾸게 되지만, 상수 타입은 아무리 여러 번
쓰더라도 사본은 딱 하나만 생기기 때문에 최종코드의 크기가 작아진다
#define 을 상수로 교체할 때의 유의 사항
1.상수 포인터 ( constant pointer ) 를 정의 하는 경우
상수 정의는 대게 Header 파일에 넣고 다른 소스 파일이 include 해서 쓰게 되므로 Pointer 는
꼭 const로 선언해주고 아울러 포인터가 가리키는 대상도 const 로 선언하는 것이 보통이다.
const char* const autorName = "Scott Meyers";
하지만 C++ 에서는 string 객체가 있으므로 다음의 선언이 더 권장된다.
const std::string authorName ( "Scott Meyers" );
2.클래스 멤버로 상수를 정의하는 경우
어떤 상수의 유효범위를 클래스로 한정하고 싶을 때 그 상수를 멤버로 만들어야 하는데
그 상수의 사본 개수가 한 개를 넘지 않게 하려면 정적( static ) 멤버로 만들어야 한다.
/* in GamPlayer.h */위의 NumTurns는 선언(declaration) 된 것이다. 정의 (definition) 이 아님을 유의하자.
class GamePlayer {
private:
static const int NumTurns = 5; // 상수 선언
int scores[NumTurns]; // 상수를 사용하는 부분
…
};
C++ 에서는 사용하고자 하는 것에 대해 정의(definition) 가 마련되어야 하는 것이 보통이지만
정적 멤버로 만들어 지는 정수류 ( 각종 정수 타입, char, bool 등 ) 타입의 클래스 상수는 예외이다.
이들에 대해 주소를 취하지 않는 한 , 정의 없이 선언만 해도 아무 문제가 없다 .
하지만 정의를 해달라고 닥달하는 구닥다리 컴파일러의 경우는
/* in GamPlayer.cpp */#define 은 일단 정의 되면 컴파일이 끝날 때 까지 유효하기 때문에 클래스 상수를 선언하는 데에는
const int GamePlayer::NumTurns;
// 정의에 값이 없는 이유는 클래스 상수의 초기값은 해당 상수가 선언된 시점에 바로 주어지기 때문
쓸 수도 없고 어떤 형태의 캡슐화 혜택도 받을 수가 없다.
오래된 컴파일러는 위의 문법이 먹히지 않는 경우가 있는데 그때는
/* in CostEstimate.h */
class CostEstimate {
private:
static const double FudgeFactor; // 정적 클래스 상수의 선언
…
};
/* in CostEstimate.cpp */
const double CostEstimate::FudgeFactor = 1.35; // 정적 클래스 상수의 정의나열자 둔갑술 ( enum hack )
클래스 상수의 초기값을 상수의 정의 시점에 둘 때 한가지 예외가 있다.
해당 클래스를 컴파일 하는 도중에 상수의 값이 필요한 때가 그때이다./* in GamePlayer.h */
class GamePLayer{
private:
int scores[NumTurns]; //이와 같은 경우
…
};컴파일러는 컴파일 과정에서 이 배열의 크기를 알아 내야 하는데 정수 타입의 정적
클래스 상수에 대한 클래스 초기화를 금지하는(표준에 어긋난 구식이지만) 구식 컴파일러의
대처 방법으로 나열자 둔갑술을 사용할 수 있다/* in GamePlayer.h */
class GamePLayer{
private:
enum { NumTurns = 5 ; } //통칭 나열자 둔갑술
int scores[NumTurns]; //해결!
…
};나열자 둔갑술의 특징
1. 동작 방식이 const보다는 #define에 더 가깝다
// const 의 주소를 얻는 것은 합당하나 enum 의 주소를 얻는 것은 합당하지 않다.
2. 어떤 형태의 쓸데없는 메모리 할당을 하지 않는다.
3. 많은 코드에서 이 기법이 사용되고 있다
Macro 함수 보다는 inline 함수를 사용하자
#define CALL_WITH_MAX( a, b ) f( (a) > (b) ? (a) : (b) ) //A와B중 큰 것을 f에 넘겨 호출
Macro작성시 기본적으로 괄호를 씌어 주어야 하나 이것으로 끝나는 것이 아니다int a = 5, b = 0;
CALL_WITH_MAX( ++a, b ); //a가 두 번 증가합니다
CALL_WITH_MAX( ++a, b+10 ); //a 가 한 번 증가합니다위와 같은 사태를 막기 위해서 inline 함수에 대한 Template 만들어 준다.
template//T가 정확히 무엇인지 모르기 때문에
inline void callWithMax( const T& a, const T& b ) //매개변수로 상수 객체의 참조자를 쓴다
{
f( a > b ? a : b );
}이 함수는 템플릿이기 때문에 동일 계열 함수군(family of functions : 하나의 템플릿을 통해
만들어 질 것으로 예측 가능한 모든 함수들을 통칭)을 만들어 낸다.
'프로그래밍 > C/C++' 카테고리의 다른 글
헝거리안 표기법 (0) | 2007.08.20 |
---|---|
UNIX 프로그래밍 필독서 (0) | 2007.08.17 |
volatile의 사용 (0) | 2007.04.20 |
[응용] printf를 잘 쓰자 (0) | 2006.08.10 |
[본문스크랩] Buffer에 대해서 생각해 봅시다... (0) | 2006.07.20 |