노무현 대통령 배너


2007. 7. 19. 11:21

#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 */
class GamePlayer {
private:
static const int NumTurns = 5; // 상수 선언
int scores[NumTurns]; // 상수를 사용하는 부분

};
위의 NumTurns는 선언(declaration) 된 것이다. 정의 (definition) 이 아님을 유의하자.
C++ 에서는 사용하고자 하는 것에 대해 정의(definition) 가 마련되어야 하는 것이 보통이지만
정적 멤버로 만들어 지는 정수류 ( 각종 정수 타입, char, bool 등 ) 타입의 클래스 상수는 예외이다.
이들에 대해 주소를 취하지 않는 한 , 정의 없이 선언만 해도 아무 문제가 없다 .
하지만 정의를 해달라고 닥달하는 구닥다리 컴파일러의 경우는
/* in GamPlayer.cpp */
const int GamePlayer::NumTurns;
// 정의에 값이 없는 이유는 클래스 상수의 초기값은 해당 상수가 선언된 시점에 바로 주어지기 때문
#define 은 일단 정의 되면 컴파일이 끝날 때 까지 유효하기 때문에 클래스 상수를 선언하는 데에는
쓸 수도 없고 어떤 형태의 캡슐화 혜택도 받을 수가 없다.

오래된 컴파일러는 위의 문법이 먹히지 않는 경우가 있는데 그때는

/* 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