센로그

[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;
}

 

 

Comments