티스토리 뷰

이펙티브 C++ 책에 대한 정리를 하려고 한다.


- #define을 쓰려거든 const, enum, inline을 떠올리자

즉, "가급적 선행 처리자보다 컴파일러를 더 가까이 하자" 라는 말이다.


#define ASPECT_RATIO 1.653

이 코드를 보면 우리눈에는 ASPECT_RATIO 라는 기호식 이름으로 보이지만 컴파일러 눈에는 그저 1.653으로 보일 뿐이다. 이는 선행처리자가 컴파일 전에 ASPECT_RATIO를 1.653로 바꾸어 버리기 때문인데.. 이 때문에 컴파일러가 쓰는 기호 테이블에 들어가지 않게 된다.

이러한 상황에서 에러 발생시 에러 메세지에는 그저 1.653만 출력될뿐.. 이게 어디서 왔는지는 알수 없다.

에러 뜨면 내가 작성한 코드라도 머리아픈데 다른 사람이 작성한 코드라 생각해보자~ 정말 끔찍하다!!!카드값


코드를 바꿔보자!

const double AspectRatio = 1.653;

const로 상수화 하여 쓴 것이다.

이제 AspectRatio는 언어 차원에서 지원하는 상수 타입의 데이터기 때문에 컴파일러의 눈에도 보이며 당연히 컴파일러가 쓰는 기호 테이블에도 들어가게 된다.


게다가 위의 예제 처럼 상수가 부동소수점 실수타입일 경우에는 컴파일을 거친 최종 코드의 크기가 #define을 썼을 때보다 적게 나올 수 있다. #define을 사용하게 되면 ASPECT_RATIO가 사용된 곳은 모두 선행처리자로 인해 1.653으로 모두 바뀌면서 결국 목적 코드 안에 1.653의 사본이 등장 횟수만큼 들어가게 되지만, 상수 타입의 AspectRatio는 아무리 여러번 쓰이더라도 사본은 딱 한 개만 생기기 때문이다.


#define을 상수로 교체하려는 분께는 딱 두가지 경우만 특별히 조심하라고 일러준다!

첫번째

상수 포인터를 정의 하는 경우이다. 상수는 대개 헤더 파일에 넣는 것이 상례이므로(다른 소스 파일이 이것을 include 해서 사용하게 됨) 포인터는 꼭 const로 선언해 주어야 하고, 포인터가 가리키는 대상까지 const로 선언하는 것이 보통이다. 만약 어떤 해더 파일 안에 char* 기반의 문자열 상수를 정의한다면 다음과 같이 const를 두 번 써야 한다.

const char* const authorName = "Scott Meyers";


// char* 기반의 구닥다리 문자열 보다 string 객체가 대체적으로 사용하기 좋다.

const std::string authorName("Scott Meyers");


두번째

클래스 맴버로 상수를 정의하는 경우이다.

어떠한 상수의 유효범위를 클래스로 한정하고자 할 때는 그 상수를 맴버로 만들어야 하는데, 그 상수의 사본 개수가 한 개를 넘지 못하게 하고 싶다면 정적 맴버로 만들어야 한다.

class GamePlayer

{

private:

static const int NumTurns = 5;    // 상수 선언

int scores[NumTurns];             // 상수를 사용하는 부분

...

};

여기서 NumTurns는 '정의' 한 것이 아닌 '선언'된 것이다.

보통 C++에서 사용하고자 하는 것에 대해 '정의'가 마련되어 있어야 하는 게 보통이지만, 정적 맴버로 만들어지는 정수류(각종 정수 타입, char, bool등) 타입의 클래스 내부 상수는 예외다. 이들에 대해 주소를 취하지 않는 한, 정의 없이 선언만 해도 아무 문제가 없게 되어 있다. 단, 클래스 상수의 주소를 구한다든지, 주소를 구하지 않는데도 우리가 쓰는 컴파일러가 미쳐서 정의를 달라고 떼쓰는 경우에는 별도의 정의를 제공해야 한다.

이렇게 클래스 상수의 정의를 구현 파일에 두면 된다.

const int GamePlayer::NumTurns;    // NumTruns의 정의

이때 정의에는 상수의 초기값이 있으면 안된다. 왜냐하면 상수의 초기값은 해당 상수가 선언된 시점에서 바로 주어지기 때문이다.


여기서 또 주의?? 할것은 클래스 상수로 #define을 사용할 생각은 절대 하지 말자~

#define은 유효범위란 게 뭔지도 모르고 매크로는 일단 정의되면 컴파일이 끝날 때까지(중간에 #undef되지만 않으면) 유효하다는 점을 기억해야 한다. 거기에 캡슐화 해택도 받을 수 없다. 말하자면 'private' 성격의 #define 같은 것은 없다는 얘기다.

이와 대조적으로 상수데이터 맴버는 캡슐화가 된다. NumTruns가 그것을 말해준다.


하지만 조금 오래된 컴파일러는 위의 문법을 받아들이지 않는 경우가 종종 있다. 정적클래스 맴버가 선언된 시점에서 초기값을 주는 것이 대개 맞지 않다고 판단하기 때문인데 어째든 위의 문법이 먹히지 않는 컴파일러를 사용 한다면 초기값을 상수 '정의'시점에 주도록 하자.

class CostEstimate

{

private:

static const double FudgeFactor;            // 헤더 파일에 정적 클래스 상수의 선언

...

};


const double CostEstimate::FudgeFactor = 1.35;    // 구현 파일에 정적 클래스 상수의 정의



왠만한 경우면 이것으로 충분하지만 딱 한가지 예외가 있다면 해당 클래스를 컴파일하는 도중에 클래스 상수의 값이 필요할 때인데, 이를테면 Gameplayer::scores 등의 배열 맴버를 선언할 때가 대표적이다. 컴파일러는 컴파일 과정에서 배열의 크기를 알아야 한다며 버틸 것이다. 그래서 구식 컴파일러에 대한 배려로서 괜찮은 방법을 추천한다면, '나열자 둔갑술' 기법이 있다.

이 기법의 원리는 나열자 타입의 값은 int 가 놓일 곳에도 쓸 수 있다는 C++의 진실을 적극 활용한다.

class GamePlayer

{

private:

enum { NumTurns= 5 };    // "나열자 둔갑술" : NumTurns를 5에 대한 기호식 이름으로 만든다.


int scores[NumTurns];    // 깔끔하게 해결

...

};



상당히 많은 경우에서 발견할 수 있는 #define 지시자의 또 다른 오용 사례는 바로 매크로 함수이다.

// 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가 한 번 증가한다.

f가 호출되기 전에 a가 증가하는 횟수가 달라진다. 바로 비교를 통해 처리한 결과가 어떤 것이냐에 따라 달라지기 때문이다.


하지만 기존 매크로의 효율을 그대로 유지함은 물론 정규 함수의 모든 동작방식 및 타입 안전성까지 완벽히 취할 수 있는 방법이 있다. 바로 인라인 함수에 대한 템플릿을 준비하는 것이다.

// T가 정확히 무엇인지 모르기 떄문에, 매개변수로 상수 객체에 대한 참조자를 쓴다.

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++의 정리를 하려 했는데.. 쓰다보니 거의 책을 옮기는 수준이 되버렸다.

그만큼 Effective C++ 책의 내용이 버릴 부분 없이 다 중요하다는 말이다.

알면서도 항상 깜박 하는데.. Effective C++ 책은 정말 항상 끼고 살아야 겠다.


출처 : Effective C++ 서적 3판 (스콧 마이어스 지음 / 곽용재 옮김)


댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
«   2024/04   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
글 보관함