[Effective C++] Item 02: #define을 쓰려거든 const, enum, inline을 떠올리자
Item 02: #define을 쓰려거든 const, enum, inline을 떠올리자
가급적 선행 처리자보다 컴파일러를 더 가까이 하자
#define
은 C++ 언어 자체의 일부가 아닌 것으로 취급될 수 있으니 const
, enum
, inline
를 가까이 하는 것이 좋다.
#define을 통한 상수 정의
#define ASPECT_RATIO 1.653
이와 같은 코드는 컴파일러에겐 보이지 않는다. 소스 코드가 컴파일러에게 넘어가기 전에 선행 처리자가 밀어버리고 숫자 상수로 바꾸어 버리기 때문에 컴파일러가 쓰는 기호 테이블에 들어가지 않는다.
따라서 숫자 상수로 대체된 코드에서 컴파일 에러가 발생한다면 이를 디버깅하기는 매우 어렵다. (컴파일러가 에러 메시지를 출력할 때 ASPECT_RATIO
가 아닌 1.653
이 출력되기 때문)
const double AspectRatio = 1.653;
대문자로만 표기하는 이름은 대개 매크로에 쓰는 것이라 이름 표기도 변경된다. 즉, C++은 상수를 대문자로 표기하는 것을 권장하지 않는다.
AspectRatio
은 언어 차원에서 지원하는 상수 타입의 데이터이기 때문에 컴파일러의 눈에도 보이고 기호 테이블에도 들어간다. 또한, 컴파일을 커진 최종 코드의 크기가 더 작아진다. 전자는 선행 처리자에 의해 코드가 전부 바뀌면서 등장 횟수만큼 들어가지만 상수 타입의 AspectRatio
는 한 번만 들어가기 때문이다.
#define 처리 과정
#define을 상수로 교체하려는 경우
상수 포인트를 정의하는 경우
상수 정의는 대개 헤더 파일에 넣는 것이 상례이므로 포인터는 꼭 const
로 선언해야 한다. 이와 아울러 상수이기 때문에 가리키는 대상까지 const
로 선언해야 한다.
// char 가변 문자열을 상수 정의의 경우
const char* const authorName = "Jeong An";
// string객체를 사용하는 경우 (C++에서는 string 객체를 사용하는 것이 좋다.)
const string authorName("Jeong An");
클래스 멤버로 상수를 정의하는 경우
클래스 상수를 정의하는 경우
어떤 상수의 유효범위를 클래스로 한정하고자 할 때는 그 상수를 멤버로 만들어야 하는데, 그 상수의 사본 개수가 한 개를 넘지 못하게 하고 싶다면 정적 멤버로 만들어야 한다.
class GamePlayer {
private:
static const int NumTurns = 5; // 클래스 상수 선언
int scores[NumTurns]; // 상수 사용
};
NumTurns
는 ‘선언’된 것으로 ‘정의’된 것이 아니다. C++의 경우 정의가 마련되어야 하는 것이 보통이지만, 정적 멤버로 만들어지는 정수류 타입의 클래스 내부 상수는 예외다. 이들에 대해 주소를 취하지 않는 한, 정의 없이 선언해도 문제가 없다.
- private 성격의 #define 같은 것은 없다.
나열자 둔갑술
나열자 둔갑술은 구식 컴파일러에서 사용하는 기법?이기 때문에 자세하게 정리하지 않는다. 하지만 책의 실용적인 이유 2번의 사항으로 사용법을 알아두는 것이 좋다.
매크로 함수
#define CALL_WITH_MAX(a, b) f((a) > (b) ? (a) : (b))
이런 식의 매크로는 단점이 한 두개가 아니다. 42서울 당시에 몇 번 사용했던 경험이 있다.
매크로의 사용에는 자율성이 매우 제한되기 때문에 매크로 자체가 원하는대로 동작하지 않거나 오용될 가능성이 높다. 따라서 당므과 같은 인라인 함수에 대한 템플릿을 준비하는 것이 좋다.
template<typename T>
inline void callWithMax(const T& a, const T& b) {
f(a > b ? a : b);
}
// T가 뭔지 모르기 때문에 매개변수로 상수 객체 대한 참조자를 쓴다.
이 함수는 템플릿이기 때문에 동일 계열 함수군을 만들어 낸다. 즉, 같은 인자 타입을 받기에 유효범위나 타입 안정성이 높아진다. but, 실수끼리 매우 근접한 값을 비교하는 경우 오류가 발생할 수 있지 않을까?
정리
- 단순한 상수를 쓸 때는, #define보다 const 객체 혹은 enum을 우선 생각합시다.
- 함수처럼 쓰이는 매크로를 만들려면, #define 매크로보다 인라인 함수를 우선 생각합시다.
느낀점
#define
에 대해서 정말 대략적으로 알고 있었는데, 책 내용을 읽고 이해 안되는 부분이 여럿있어서 관련 내용을 찾아보며 이해하게 된 것 같다. 그냥 마음 편하게 const로 시작하자.
C#
에서 주로 사용하는 readonly
와 같이 동적으로 할당하는 상수의 개념이 있는지 찾아봤지만 없다고 한다. 대신 const와 setter를 활용할 수 있는 것 같다. 번외로 컴파일 타임 상수인 constexpr
가 있다.
댓글남기기