KoreanFoodie's Study

Effective C++ | 항목 2 : #define 을 쓰려거든 const, enum, inline 을 떠올리자 본문

Tutorials/C++ : Advanced

Effective C++ | 항목 2 : #define 을 쓰려거든 const, enum, inline 을 떠올리자

GoldGiver 2022. 10. 25. 16:02

C++ 프로그래머의 필독서이자 바이블인, 스콧 마이어스의 Modern Effective C++ 를 읽고 기억할 내용을 요약하고 있습니다. 꼭 읽어보시길 추천드립니다!

항목 2 : #define 을 쓰려거든 const, enum, inline 을 떠올리자

핵심 :

1. 단순한 상수를 쓸 때는, #define 보다 const 객체 혹은 enum 을 우선 생각하자.
2. 함수처럼 쓰이는 매크로를 만들려면, #define 매크로보다 인라인 함수를 우선 생각하자.

 

항목 2의 핵심은, 가급적 선행 처리자보다는 컴파일러와 친하게 지내는 게 좋다는 것이다. 다음과 같은 예를 보자.

#define ASPECT_RATIO 1.653

위의 경우, 해당 부분을 디버깅할 때, ASPECT_RATIO 는 컴파일러가 쓰는 기호 테이블에 들어가지 않아 에러 메시지에는 1.653 이 뜨게 된다. 위의 방식 대신, 다음과 같이 쓰는 것이 좋다.

const double AspectRatio = 1.653;

위와 같이 쓰면, 목적 코드의 크기도 줄어든다. (#define 은 1.653 의 사본이 등장 횟수만큼 들어가지만, 상수 타입은 사본이 딱 한개만 생김)

 

단, #define 을 상수로 교체할 때, 다음과 같은 두 케이스를 조심하자.

1. 상수 포인터 (constant pointer) 를 정의하는 경우

상수 정의를 하게 되면 pointer 는 꼭 const 로 선언하자.

// C 스타일의 문자열 상수를 가리키는 상수 포인터
const char * const authorName = "Scott Meyers";

// C++ 스타일 (권장)
const std::string authourName("Scott Meyers");

 

2. 클래스 멤버로 상수를 정의하는 경우 (클래스 상수 정의)

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

class SingletonIsBad
{
  private:
    static const int Numturns = 5; // 상수 선언
    int scores[NumTurns]; // 상수를 사용하는 부분
}

 

위의 NumTurns 는 '선언' 된 것이다('정의' 가 아님). 정적 멤버로 만들어지는 정수류(int, char, bool 등) 타입의 클래스 내부 상수는 이들에 대해 주소를 취하지 않는 한, 정의 없이 선언만 해도 문제가 없다. 물론 클래스 상수의 주소를 구하는 경우에는 다음과 같이 별도의 정의를 제공해야 한다.

// '선언' 이 아니라 '정의' 이다. 그런데 왜 값이 주어지지 않았을까?
// 클래스 상수의 정의는 구현 파일에 두며, 헤더 파일에는 두지 않는다.
// 정의에는 상수의 초기값이 있으면 안되는데, 
// 왜냐하면 클래스 상수의 초기값은 해당 상수가 선언된 시점에 바로 주어지기 때문이다!
const int SingletonIsBad::NumTurns;

// 조금 오래된 컴파일러의 경우, 정의하는 경우 초기값을 부여하기도 한다. 아주 가끔.
// 따라서 선언부에서는 초기값을 주지 말고, 정의부에서 다음과 같이 써야 한다.
const int SingleTonIsBad::NumTurns = 5;

// 위의 경우에서 scores[NumTurns] 처럼 선언 이후 배열의 크기를 받아오려면 어떻게 해야 할까?
// 그런 경우, 다음과 같은 '나열자 둔갑술(enum hack)' 을 쓰자.
class SingleTonIsBad
{
  private:
    enum {NumTurns = 5 }; // NumTurns 를 5에 대한 기호식 이름으로 만든다.
    int scores[NumTurns]; // 깔끔하게 해결!
}

참고로, 클래스 상수를 #define 하는 방법 같은건 없다(캡슐화 같은 것도 없다).

나열자 둔갑술은 const 보다 #define 에 더 가깝다. #define 과 enum 의 주소를 취하는 것은 불법이기 때문이다. 

 

#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 가 한 번 증가함

위와 같은 괴현상을 방지하기 위해, 다음과 같이 템플릿 인라인 함수를 만들자.

template <typename T>
inline void CallWithMax(const T& a, const T& b)
{
  f(a > b ? a : b);
}

 

 

Comments