Effective C++ 3rd Edition. Scott Meyers.
C++ 프로그래머의 필독서라고 불리는 Effective C++을 읽고 중요한 내용을 정리한 글 입니다.
Item2. #define을 쓰려거든 const, enum, inline을 떠올리자.
🔻#define보다는 const, enum
item이름을 다른 말로 해보면 "가급적 전처리기보다는 컴파일러를 더 가까이 하자"라고 할 수 있다.
#define ASPECT_RATIO 1.653
위와 같은 코드를 썻을 때, 우리는 ASPECT_RATIO가 Symbolic name으로 보이지만, 소스코드가 컴파일러에게 넘어가기 이전에 전처리기가 해당 글자를 숫자 상수로 바꾸어버리기 때문에 컴파일러의 입장에서는 전혀 보이지 않는다. 그렇기때문에 해당 상수부분에서 에러가 발생하면 어디서 에러가 발생했는지 파악이 힘든 곤란한 상황이 올 수 있다.
이러한 문제를 해결하기 위한 방법은 매크로대신 상수를 쓰는 것이다.
const double AspectRatio = 1.653;
이런식으로 상수로 정의해주게 되면 컴파일러의 눈에도 보이게 된다. 게다가 위처럼 상수가 부동소수점 타입인 경우 코드의 크기가 매크로를 썻을때보다 더 작게 나올 수 있다.(상수는 여러번 쓰이더라도 한개만 존재하지만, 매크로는 모든 ASPECT_RATIO를 1.653으로 바꾸어버리기 때문)
❗ #define을 상수로 교체할때 주의할 두가지
1. 상수 포인터를 정의하는 경우.
상수 정의는 대개 헤더 파일에 넣는 것이 상례이므로, 포인터는 꼭 const로 선언해주어야 하고, 포인터가 가리키는 대상까지 const로 선언하는 것이 보통이다.
const char* const authorName = "Scott Meyers";
/* 문자열 상수를 쓸때는 char* 기반의 구식 문자열
보다 string 객체가 사용하기 더 좋다. */
const std::string authorName("Scott Meyers");
2. 클래스 상수를 정의하는 경우.
어떤 상수의 유효범위를 클래스로 한정하고자 할 때, 그 상수를 멤버로 만들어야 하는데, 그 상수의 사본 개수가 한개를 넘지 못하게 하고싶다면 정적(static) 멤버로 만들어야한다.
// GamePlayer.h
class GamePlayer
{
private:
static const int NumTurns = 5; // 상수 선언
int scores[NumTurns]; // 상수를 사용하는 부분
};
위의 NumTurns는 '정의'된 것이 아닌 '선언'된 것이므로 주의하자. (정적 멤버로 만들어지는 정수류 타입의 클래스 내부 상수는 예외적으로 정의를 해주지 않아도된다.)
클래스 상수의 주소를 구한다든지, 컴파일러의 문제로 정의를 요구할때는 다음과 같이 정의를 제공하면된다.
//GamePlayer.cpp
const int GamePlayer::NumTurns;
해당 정의는 구현 파일에 두어야하고. 정의에는 상수의 초기값이 있으면 안된다.(클래스 상수의 초기값은 해당 상수가 선언된 시점에 주어진다.)
❓#define으로 클래스 상수를 정의한다면 어떻게 될까?
방법 자체가 말이안된다. 유효범위가 정해지지도 않고 어떤 형태의 캡슐화 혜택도 받을 수 없다.
🔻매크로 함수보다는 inline
#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부분이 ++a로 대체가 되는거라서 비교를 통해 처리한 결과가 어느것이냐에 따라 결과가 달라진다.
C++에는 함수 호출을 없애준다는 명목 하에 문제점이 많은 매크로 함수를 쓸 필요없이, 기존의 매크로의 효율은 그대로 유지한채, 정규함수의 모든 동작방식 및 타입 안전성을 완벽히 취할 수 있다. 바로 인라인 함수에 대한 템플릿을 만드는 것이다.
template <typename T>
inline void callWithMax(const T& a, const T& b)
{
f(a > b ? a : b);
}
이런 식으로 하면 함수 내부에 괄호를 도배할 일도 없어지고, 인자를 여러번 평가할 지도 모른다는 위험성에서도 없애준다. 또한 진짜 함수이기 때문에 유효범위 및 접근규칙도 그대로 따라간다.
💡 핵심!
🔸 단순한 상수를 쓸 때는 #define보다 const 객체 혹은 enum을 우선 생각하자.
🔸 함수처럼 쓰이는 매크로를 만들려면, #define 매크로보다 인라인 함수를 우선 생각하자.
'C++ > Effective C++' 카테고리의 다른 글
[Effective C++] 6. 컴파일러가 만들어낸 함수가 필요없으면 확실히 이들의 사용을 금해버리자 (0) | 2022.11.10 |
---|---|
[Effective C++] 5. C++가 은근슬쩍 만들어 호출해 버리는 함수들에 촉각을 세우자 (0) | 2022.11.10 |
[Effective C++] 4. 객체를 사용하기 전에 반드시 그 객체를 초기화하자 (0) | 2022.11.09 |
[Effective C++] 3. 낌새만 보이면 const를 들이대 보자! (0) | 2022.11.09 |
[Effective C++] 1. C++을 언어들의 연합체로 바라보자. (2) | 2022.11.08 |
게임개발자를 꿈꾸는 대학생의 개발 공부 블로그
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!