센로그
[EC++] 2. #define을 쓰려거든 const, enum, inline을 떠올리자 본문
Effective/Effective C++
[EC++] 2. #define을 쓰려거든 const, enum, inline을 떠올리자
seeyoun 2024. 11. 12. 23:42가급적 전처리기보다 컴파일러를 더 가까이 하자.
- #define을 쓰면, 전처리기가 텍스트를 상수로 대체해서 컴파일러에게 넘긴다.
- 컴파일러의 기호 테이블에도 이름이 들어가지 않는다.
매크로 상수보다는 const나 enum을 쓰자.
- 매크로 상수
- 전처리기가 해주는 단순한 텍스트 치환이다.
- 스코프 개념 없이 전역적이다.
- 타입 안정성도 없다.
- 메모리에 안 올라가며, 따라서 주소 참조도 안된다.
- const 객체
- 컴파일러가 처리해준다.
- 기호 테이블(symbol table)에 넣어주므로 타입 검사가 가능하다.
- 따라서 특정 스코프 내에서만 사용 가능하도록 할 수도 있다.
- 메모리에 존재하며, 따라서 주소를 참조할 수도 있다.
- enum 둔갑술 (enum hack)
- 컴파일러가 처리해준다.
- int 상수가 필요할 때, enum 둔갑술을 사용하면 어느정도 #define에 가깝게 동작하면서도 유연하고 안전하게 사용 가능하다.
- 메모리에 안 올라가며, 따라서 주소 참조도 안된다.
- enum 자체의 주소 참조가 안된다는 것이다. enum 객체의 주소 참조는 된다.
매크로 함수보다는 inline을 쓰자.
- 매크로 함수는 의도치 않은 다양한 부작용이 발생할 가능성이 크다.
- 매개변수에 괄호를 안 씌울 때의 문제점
- 인자로 증감 연산자를 사용할 때의 문제점
ex) 두 인자 중 더 큰 값을 선택하여 함수 f에 전달하는 함수를 보자.
#define CALL_WITH_MAX(a, b) f((a) > (b) ? (a) : (b))
여기에 증감 연산자를 사용하는 파라미터를 전달하면, 의도치 않은 동작이 발생한다.
int a = 5, b = 0;
CALL_WITH_MAX(++a, b); // a > b 라서 f에 a를 전달함. a가 두 번 증가함
CALL_WITH_MAX(++a, b+10); // a < b 라서 f에 b를 전달함. a가 한 번 증가함.
- 템플릿 + 인라인을 쓰면, 매크로 함수의 효율 + 동작 방식 + 타입 안정성까지 가져갈 수 있다.
- 동작 방식: 템플릿 함수는 컴파일 타임에 특정 타입으로 인스턴스화 된다.
- 타입 안정성: 이때 컴파일러에 의해 타입 검사가 수행된다.
- 효율: 인라인이 적용되면 실제 코드부분이 치환되어, 런타임 호출 오버헤드가 사라진다.
ex) 위 예제를 템플릿 + 인라인으로 구현하면 다음과 같다.
template<typename T>
inline void callWithMax(const T& a, const T& b){
f( a > b ? a : b);
}
이 경우, callWithMax(++a, b) 로 호출하더라도 a는 항상 한번만 증가한다.
나의 생각
+) 매크로 상수, const, enum
- 출력은 6으로 똑같지만 작동하는 방식이 다르다.
- 전처리기가 상수로 대체한 CNT는 치환된 값만 나타남
- cnt는 변수로 취급되며, 주소 참조도 가능함. (메모리에 있음)
- 주소 참조 하는 게 싫으면, enum으로 만들면 주소 참조 못함. (메모리에 없음)
+) 매크로 함수 매개변수에 괄호 안 씌울 때 발생할 수 있는 문제점
단순 텍스트 치환이므로 다양한 문제점이 발생한다.
1. 연산 우선순위 오류
#define SQUARE(x) x * x
int result = SQUARE(2 + 3);
- 예상: (2 + 3) * (2 + 3) = 25
- 결과: 2 + 3 * 2 + 3 = 11
2. 복합 표현식일 때 오류
#define ADD(x, y) x + y
int result = ADD(1, 2) * 3;
- 예상: (1 + 2) * 3 = 9
- 결과: 1 + 2 * 3 = 7
+) 매크로 함수의 추가적인 부작용
증감 연산자를 넣은 경우의 오류
#define DOUBLE(x) (x) + (x)
int i = 3;
int result = DOUBLE(i++);
- i값 예상: 4
- i값 결과: 5 (매크로 실행 과정에서 두 번 증가함)
+) 매크로 함수 vs 템플릿+인라인 예제 코드
#include <iostream>
void f(int x) {
std::cout << x << std::endl;
}
// 1. 매크로 함수
#define CALL_WITH_MAX(a, b) f((a) > (b) ? (a) : (b))
// 2. 템플릿 + 인라인
template<typename T>
inline void callWithMax(const T& a, const T& b) {
f(a > b ? a : b);
}
int main() {
int a = 5, b = 0;
CALL_WITH_MAX(++a, b); // a = 7
CALL_WITH_MAX(++a, b + 10); // a = 8
callWithMax(++a, b); // a = 9
callWithMax(++a, b + 10); // a = 10
return 0;
}
'Effective > Effective C++' 카테고리의 다른 글
[EC++] 6. 컴파일러가 만들어낸 함수가 필요 없으면 확실히 이들의 사용을 금해 버리자 (2) | 2024.11.13 |
---|---|
[EC++] 5. C++가 은근슬쩍 만들어 호출해 버리는 함수들에 촉각을 세우자 (2) | 2024.11.13 |
[EC++] 4. 객체를 사용하기 전에 반드시 그 객체를 초기화하자 (0) | 2024.11.13 |
[EC++] 3. 낌새만 보이면 const를 들이대 보자! (6) | 2024.11.13 |
[EC++] 1. C++를 언어들의 연합체로 바라보는 안목은 필수 (0) | 2024.11.12 |
Comments