[Effective C++] 2. #define을 쓰려거든 const, enum, inline을 떠올리자
주제에 더 가까운 제목은 가급적 선행 처리자보다 컴파일러를 더 가까이 하자.라고 한다.
#define 상수의 단점
#define ASPECT_RATIO 1.653
ASPECT_RATIO가 기호식 이름으로 보이지만 컴파일러에게 넘어가기 전에 전처리기가 숫자 상수(1.653)로 바꿔버리기 때문에 ASPECT_RATIO라는 이름은 컴파일러가 쓰는 기호 테이블에 들어가지 않는다.
그래서 숫자 상수로 대체된 코드에서 컴파일 에러라도 발생하게 되면 에러 찾기가 꽤나 힘들 수 있다. (소스코드엔 ASPECT_RATIO, 에러 메시지는 1.653)
또, 기호 테이블에 이름이 없기 때문에 기호식 디버거에서도 문제가 나타날 수 있다.
해결 방법 : const
const double AspectRatio = 1.653;
상수 타입의 데이터이기 때문에 기호 테이블에도 들어간다.
const를 사용할 때 #define보다 최종 코드의 크기가 더 작게 나올 수 있는데, 이는 매크로를 사용 시 ASPECT_RATIO가 등장하기만 하면 전처리기에 의해 모두 1.653으로 바꿔버리면서 사본이 등장 횟수만큼 들어가게 되지만, 상수 타입의 AspectRatio는 아무리 여러 번 쓰여도 사본은 딱 한 개만 생기기 때문이다.
#define을 상수로 교체 시 주의점
1. 상수 포인터를 정의하는 경우
상수 정의는 대개 헤더 파일에 넣는 것이 상례이므로(다른 소스파일이 이것을 인클루드 해서 쓰게 된다.) 포인터는 꼭 const로 선언해주어야 하고, 포인터가 가리키는 대상까지 const로 선언하는 것이 보통이다.
const char* const authorName = "Scorr Meyers";
문자열 상수를 쓸 때는 char*기반의 예전 문자열보다는 string객체가 대체적으로 사용하기 좋음.
const std::string authorName("Scott Meyers");
2. 클래스 멤버로 상수를 정의하는 경우
상수의 유효 범위를 클래스로 한정하고자 할 때는 그 상수를 멤버로 만들어야 하는데, 그 상수의 사본 개수가 한 개를 넘지 못하게 하고 싶다면 정적(static) 멤버로 만들어야 한다.
class GamePlayer
{
private:
static const int NumTurns = 5; // 상수 선언
int scores[NumTurns]; // 상수를 사용하는 부분
}
위의 NumTurns는 '선언(declaration)'된 것으로 '정의'가 아니니 주의해야 한다.
보통 사용하고자 하는 것에 대해 '정의'가 마련되어야 하는 게 보통이지만, 정적 멤버로 만들어지는 정수류(각종 정수 타입, char, bool 등) 타입의 클래스 내부 상수는 예외이다.
이들에 대해 주소를 취하지 않는 한, 정의 없이 선언만 해도 아무 문제가 없게 되어있다. 단, 클래스 상수의 주소를 구한다든지, 주소를 구하지 않더라도 사용하는 컴파일러가 잘못 만들어지는 관계로 정의를 달라고 하는 경우에는 아래처럼 별도의 정의를 제공해야 한다.
const int GamePlayer::NumTurns; // NumTurns의 정의. 값이 주어지지 않는 이유는 아래에..
이때 클래스 상수의 정의는 구현 파일에 두고 헤더 파일엔 두지 않는다. 정의에는 상수의 초기값이 있으면 안 되는데, 왜냐면 클래스 상수의 초기값은 해당 상수가 선언된 시점에서 바로 주어지기 때문이다.(즉, NumTurns는 선언될 당시에 바로 초기화되어야 함)
컴파일러가 오래돼서 위의 문법을 받아들이지 않는다면 초기값을 상수의 '정의' 시점에 주면 웬만한 경우 해결된다.
class CostEstimate
{
private:
static const double FudgeFactor;
}
---CostEstimate.cpp---
const double CostEstimate::FudgeFactor = 1.35f;
Enum hack (나열자 둔갑술)
위의 컴파일러가 오래된 경우 초기값을 상수의 '정의'시점에 정의해주면 웬만한 경우 해결되지만, 해당 클래스를 컴파일하는 도중에 클래스 상수의 값이 필요할 때 예외가 발생하게 된다.
이를테면 GamePlayer::scores 등의 배열 멤버를 선언할 때 컴파일러는 컴파일 과정에서 배열의 크기를 알아야 한다고 할 것이므로, 이러한 경우가 대표적인 예이다.
그렇기 때문에 구식 컴파일러에서 이를 피하기 위해 사용하는 기법으로 '나열자 둔갑술'로 알려진 기법을 사용할 수 있다.
이 기법의 원리는 나열자 타입의 값은 int가 놓일 곳에도 쓸 수 있다는 C++의 특성을 적극 활용하는 것으로 다음과 같이 정의할 수 있다.
class GamePlayer
{
private:
enum { NumTurns = 5 };
int scores[NumTurns];
}
Enum을 알아두어야 하는 이유
- 동작 방식이 const보다는 #define에 가깝다.
- #define처럼 enum의 주소를 취하는 일은 합당하지 않다.
- #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가 한번 증가
이는 매크로 함수에서 비교를 통해 처리한 결과에 따라 a 호출 빈도가 달라지기 때문이다.
해결방법 : 인라인 함수에 대한 템플릿
templete<typename T>
inline void callWithMax(const T& a, const T& b) // T가 정확히 무엇인지 모르기때문에 매개변수로 상수 객체에 대한 참조자를 사용함
{
f(a > b ? a : b);
}
이 함수는 템플릿이기 때문에 동일 계열 함수 군을 만들어 낸다. 또한 진짜 함수기 때문에 유효 범위 및 접근 규칙을 그대로 따라가며 함수 콜 또한 발생하지 않는다.
요약
- 단순한 상수를 쓸 때는 #define보다 const 객체 혹은 enum을 우선 생각하자.
- 함수처럼 쓰이는 매크로를 만들려면, #define 매크로보다 인라인 함수를 우선 생각하자.
'Books > Effective C++' 카테고리의 다른 글
[Effective C++] 6. 컴파일러가 만들어낸 함수가 필요 없으면 확실히 이들의 사용을 금해 버리자 (0) | 2022.05.09 |
---|---|
[Effective C++] 5. C++가 은근슬쩍 만들어 호출해 버리는 함수들에 촉각을 세우자 (0) | 2022.05.08 |
[Effective C++] 4. 객체를 사용하기 전에 반드시 그 객체를 초기화하자 (0) | 2022.05.07 |
[Effective C++] 3. 낌새만 보이면 const를 들이대 보자! (0) | 2022.05.07 |
[Effective C++] 1. C++을 언어들의 연합체로 바라보는 안목은 필수 (0) | 2022.05.04 |
댓글
이 글 공유하기
다른 글
-
[Effective C++] 5. C++가 은근슬쩍 만들어 호출해 버리는 함수들에 촉각을 세우자
[Effective C++] 5. C++가 은근슬쩍 만들어 호출해 버리는 함수들에 촉각을 세우자
2022.05.08 -
[Effective C++] 4. 객체를 사용하기 전에 반드시 그 객체를 초기화하자
[Effective C++] 4. 객체를 사용하기 전에 반드시 그 객체를 초기화하자
2022.05.07 -
[Effective C++] 3. 낌새만 보이면 const를 들이대 보자!
[Effective C++] 3. 낌새만 보이면 const를 들이대 보자!
2022.05.07 -
[Effective C++] 1. C++을 언어들의 연합체로 바라보는 안목은 필수
[Effective C++] 1. C++을 언어들의 연합체로 바라보는 안목은 필수
2022.05.04