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

2024. 12. 16. 20:48·책/Effective C++

#define의 문제점

#define은 전처리 지시문으로, 컴파일러가 코드를 읽기 전에 텍스트 치환이 이루어집니다.

#define ASPECT_RATIO 1.653

컴파일러는 ASPECT_RATIO라는 이름을 알 수 없고, 단지 1.653만을 인식합니다. 그 결과 ASPECT_RATIO라는 이름은 컴파일러가 쓰는 기호 테이블에도 들어가지 않습니다.

이로 인해 에러 메시지엔 1.653만 보여 코드 분석과 디버깅이 어려워집니다.

 

대안 1 : const

const double AspectRatio = 1.653;

AspectRatio는 상수 타입의 데이터이므로 컴파일러의 눈에도 보이며 기호 테이블에도 들어갑니다.

 

상수로 교체하려는 경우 조심해야 할 두 가지 경우

1. 상수 포인터(Constant Pointer)의 정의

포인터를 상수로 정의할 때, 포인터 자체를 상수로 만들 것인지, 포인터가 가리키는 값을 상수로 만들 것인지 헷갈릴 수 있습니다.

const char* const authorName = "Scott Meyers"; //const를 두 번 사용

개선 방안: C++에서는 char*보다 string을 사용하는 것이 더 안전하고 편리

const std::string authorName = "Scott Meyers";

 

2. 클래스 내 정적 상수(static const)의 정의

class GamePlayer {
private:
    static const int NumTurns = 5; // 정적 상수 선언
    int scores[NumTurns];         // 배열 크기에 사용
};

위의 NumTruns는 ‘선언(declaration)’된 것입니다. ‘정의’가 아닙니다.

  • 정적 상수는 클래스 내부에서 선언만 하고, 클래스 외부에서 정의해야 합니다.
  • 단, 정수형 정적 상수(각종 정수 타입, char, bool 등)는 클래스 내부에서 선언과 동시에 초기화할 수 있으며, 따로 정의할 필요가 없습니다.

단, 클래스 상수의 주소를 구하는 경우에는 다음과 같이 별도의 정의를 제공해야 합니다.

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

클래스 상수의 정의는 헤더 파일이 아닌 구현 파일에 둡니다.

클래스 상수의 초기값은 해당 상수가 선언된 시점에 바로 주어지기 때문에 정의에는 상수의 초기값이 있으면 안 됩니다.

 

대안 2: enum

예외 상황: 컴파일 타임에 값이 필요한 경우

NumTurns와 같은 정적 상수를 배열 크기와 같이 컴파일 타임에 필요한 경우에는 일부 컴파일러가 클래스 내부에서의 초기화를 허용하지 않을 수 있습니다. 이 문제를 해결하기 위해 enum hack을 사용할 수 있습니다.

class GamePlayer {
private:
    enum { NumTurns = 5 }; // NumTurns를 5에 대한 기호식 이름으로 만든다
    int scores[NumTurns];  // 배열 크기 정의
};

Enum Hack(나열자 둔갑술)이란 정적 상수 대신 enum을 사용해 정수 값을 정의하는 방법입니다. 열거형(enum)의 값은 컴파일 타임 상수로 취급되며, 정적 상수의 역할을 대신할 수 있습니다.

  • 동작 방식은 const보다 #define에 더 가깝습니다. 메모리를 추가로 할당하지 않습니다.
  • 템플릿 메타프로그래밍의 핵심 기법입니다.

 

대안 3: inline

함수처럼 보이는 매크로는 inline 함수로 대체하는 것이 좋습니다.

매크로 함수라고 부르지만 단순히 치환하기만 하므로 실제로 함수는 아닙니다. 단순히 텍스트를 치환하는 방식으로 동작하므로 전달된 인수가 매크로 내부에서 여러 번 평가될 수 있어 문제를 발생합니다.

// 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) > (b) ? (++a) : (b) )

a의 값이 1 증가하고, a는 6이 됩니다. (6 > 0)는 참이므로, 다시 ++a를 실행하면, a가 또 1 증가하여 7이 됩니다.

두 번째 호출: f( (++a) > (b+10) ? (++a) : (b+10) )

비교식에서 (8 > 10)는 거짓이므로, b+10이 평가되므로 a는 한 번만 증가합니다.

 

비교를 통해 처리한 결과가 어떤 것이냐에 따라 a가 증가하는 횟수가 달라지는 문제점이 있습니다. 매크로 대신 inline 함수를 사용하면, 인수가 정확히 한 번만 평가되므로 이러한 문제를 방지할 수 있습니다.

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

 

중요!

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

 

 

 

'책 > Effective C++' 카테고리의 다른 글

[Effective C++] 항목 3: 낌새만 보이면 const를 들이대 보자!  (0) 2024.12.17
[Effective C++] 항목 1: C++를 언어들의 연합체로 바라보는 안목은 필수  (1) 2024.12.05
'책/Effective C++' 카테고리의 다른 글
  • [Effective C++] 항목 3: 낌새만 보이면 const를 들이대 보자!
  • [Effective C++] 항목 1: C++를 언어들의 연합체로 바라보는 안목은 필수
개발자 밍
개발자 밍
dev0404 님의 블로그 입니다.
  • 개발자 밍
    Developer
    개발자 밍
  • 전체
    오늘
    어제
    • 분류 전체보기 (88)
      • 강의 (8)
        • UE Climbing System (3)
        • UE Dungeon (1)
        • HCI (4)
      • 책 (18)
        • 객체지향의 사실과 오해 (5)
        • Effective C++ (3)
        • 이득우의 게임 수학 (4)
        • 이것이 취업을 위한 컴퓨터 과학이다 (4)
        • 리뷰 (2)
      • C++ (2)
      • 알고리즘 (2)
      • 자료구조 (1)
      • Unreal (4)
      • 내일배움캠프 (52)
        • TIL (52)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    알고리즘
    그래픽스
    Effective
    객체지향
    자료구조
    언리얼
    내일배움캠프
    게임수학
    컴퓨터 구조
    컴퓨터구조
    c++
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.1
개발자 밍
[Effective C++] 항목 2: #define을 쓰려거든 const, enum, inline을 떠올리자
상단으로

티스토리툴바