💙ref.
스콧 마이어스, Effective C++ [3판], 곽용재(역), 프로텍미디어, 2015
2. #define
항목 2:
#define
을 쓰려거든const, enum, inline
을 떠올리자
define 코드 예제
1
#define ASPECT_RATIO 1.653
소스 코드가 컴파일러로 넘어가기 전에 선행 처리자가 해당 코드를 숫자 상수로 변환시킨다. 결과적으로, ASPECT_RATIO
라는 이름은 컴팡리러가 쓰는 기호 테이블에 들어가지 않는다!
숫자 상수로 대체된 코드에서 컴파일 에러가 발생하면 곤란한 상황이 발생한다,
- 소스 코드엔
ASPECT_RATIO
가 있었는데 에러 메시지엔1.653
가 출력됨- 만약, 해당
define
이 정의된 코드가 직접 작성한 것이 아니라면 어느 코드인지 찾는 데 시간을 낭비하게 됨 - 기호식 디버거에도 같은 문제가 발생할 수 있음(기호 테이블에 들어가지 않기 때문)
- 만약, 해당
위 예제 코드의 해결안
1
2
const double AspectRatio = 1.653
// 대문자로만 표기하는 이름은 대개 매크로에 쓰는 것이므로, 이름 표기도 바꿔주어야 한다!
- 언어 차원에서 지원하는 상수 타입의 데이터이므로 컴파일러도 인식 가능
- 컴파일러 기호 테이블에 저장
- 상수가 부동소수점 실수 타입일 경우, 컴파일을 거친 최종 코드의 크기가
#define
보다 작게 나올 수 있음#define
의 경우1.653
의 사본이 등장 횟수마다 만들어지지만, 상수 타입일 경우 딱 한 개만 생성
1. #define
=> 상수 교체 시 주의사항
1.1. 상수 포인터 정의
-
상수 포인터(
constant pointer
) 정의-
대개 헤더 파일에서 상수를 정의하므로, 포인터를 반드시
const
로 선언 -
포인터가 가리키는 대상도 함께
const
로 선언하는 것이 일반적임eg) 어떤 헤더 파일 안에
char*
기반의 문자열 상수를 정의할 경우,const
를 두 번 기재const char * const authorName = "Scott Meyers";
const std::string authorName("Scott Meyers");
=>string
객체 사용을 권장!
-
2. 클래스 멤버로 상수 정의
어떤 상수의 유효범위를 클래스로 한정할 경우, 사본 개수가 한 개를 넘지 못하게 하고 싶다면 정적 멤버로 선언하자.
1
2
3
4
5
6
class GamePlayer{
private:
static const int NumTurns = 5; // 상수 선언
int scores[NumTurns]; // 상수를 사용하는 부분
...
};
-
코드 작성 시 선언 후 정의부를 작성하는 것이 일반적임
-
정적 멤버로 만들어지는 정수류 타입의 클래스 상수는 정의 마련하지 않음
-
클래스 상수의 주소를 구할 경우에만 정의부 구현
const int GamePlayer::NumTurns;
- 별도의 정의를 기재할 경우, 값을 제공하지 않음
- 이때, 클래스 상수의 정의는 구현 파일에 기재
- 클래스 상수의 초기값은 해당 상수가 선언된 시점에 바로 초기화
- 별도의 정의를 기재할 경우, 값을 제공하지 않음
-
상수 대체 시 주의사항
-
클래스 상수는
#define
으로 만들지 말자-
#define
은 클래스 상수를 정의하는데 사용할 수 x -
어떤 형태의 캡슐화 혜택도 받을 수 없음
eg)
private
형태의#define
구문은 존재하지 않음
-
1.2. 클래스 멤버 초기화가 어려운 경우
컴파일러가 정적 클래스 멤버가 선언된 시점에 초기값을 주는 것이 맞지 않는다고 판단할 경우, 초기값은 상수 정의 시점에 기재하자.
1
2
3
4
5
6
7
8
class CostEstimate {
private:
static const double FudgeFactor;
...
};
// 정적 클래스 상수의 정의 => 구현 파일에 적자!
const double CostEstimate::FudgeFactor = 1.35;
-
이때, 해당 클래스를 컴파일하는 도중 클래스 상수의 값이 필요한 경우가 생김
-
GamePlayer::scores
과 같이 배열 멤버 선언=> “나열자 둔갑술(
enmu hack
)” 기법을 사용해 해결 가능
-
1
2
3
4
5
6
7
8
class GamePlayer {
private:
// "나열자 둔갑술": NumTurns를 5에 대한 기호식 이름으로 만든다.
enum { NumTurns = 5 };
int scores[NumTurns];
...
};
-
나열자 둔갑술
-
동작 방식이
const
보다#define
에 가까움-
const
의 주소를 잡아내는 것은 합당하나,enum
의 주소를 함부로 잡을 수 x(=>
#define
과 유사함!!) -
개발자가 선언한 정수 상수의 주소를 다른 사람이 얻는 게 싫을 경우
-
const
객체에 대한 메모리를 만들고 싶지 않을 경우(=>
enum
의 경우 어떤 형태의 쓸데없는 메모리 할당을 하지 않음)
-
-
템플릿 메타프로그래밍의 핵심 기술이 됨
-
2.1. 오용 사례
매크로 함수
1
2
// a와 b 중 큰 것을 f로 넘김
#define CALL_WHTH_MAX(a, b) f((a) > (b) ? (a) : (b))
- 함수처럼 보이지만 함수 호출 오버헤드를 일으키지 않는 매크로 구현
- 이런 식의 매크로는 단점이 많음. 권장하지 않는다.
- 매크로 본문에 들어 있는 인자마다 반드시 괄호를 씌워 주자.
- 괄호가 없으면 표현식을 매크로에 넘길 때 오류가 발생할 수 있음
1
2
3
4
int a = 5, b = 0;
CALL_WHTH_MAX(++a, b); // a 두 번 증가
CALL_WHTH_MAX(++a, b+10); // a 한 번 증가
f
가 호출되기 전에a
가 증가하는 횟수가 달라짐- 비교를 통해 처리한 결과가 어떤 것이냐에 따라 달라진다(프로그램에 올 발생)
단점 개선한 코드
1
2
3
4
5
template<typename T>
inline void callWithMax(const T& a, const T& b)
{
f(a > b ? a : b);
}
-
템플릿이기 때문에 동일 계열 함수군(family of functions)을 생성
-
동일한 타입의 객체 두 개를 인자로 받고 둘 중 큰 것으로
f
로 넘겨 호출 -
함수 본문에 괄호를 쓸 필요 x
-
인자를 여러 번 평가하지 않음
-
callWithMax
는 실제로 구현된 함수이므로 유효범위 및 접근규칙을 그대로 따라감-
cf) 임의의 클래스에서만 사용할 수 있는 인라인 함수에 대해서도 문제가 발생하지 않음
매크로는 이런 이야기에 대해 관련 x
-
-
*🪄Conclusion)* **
1. 단순한 상수를 쓸 때는, #define
보다 const
혹은 enum
을 우선 생각하자.
2. 함수처럼 보이는 매크로를 만들려면 #define
보다 인라인 함수를 우선 생각하자.
Leave a comment