윈도우 프로그래밍에서 빼놓지 않고 등장하는 헤더가 무엇일까? Hello World!를 찍을 때 필요한 <stdio>나 <iostream>? C/C++의 세계를 아울러 보면 그럴지도 모른다. 그러나 윈도우 프로그래밍에만 등장하는 헤더를 놓고 따진다면 <windows.h>도 꽤 강력한 후보이리라 생각한다. <windows.h>는 자주 쓰는 각종 헤더와 타입, 매크로 등을 한데 묶었기 때문에 보통 미리 컴파일된 헤더에 놓고 이용한다. 그런데 이 헤더가 항상 아름답지는 않다. 특히 자주 쓰는 이름을 매크로 이름으로 정의한 탓에 신경을 박박 긁기도 한다. min, max가 그런 매크로 중 하나다.
// windows.h 의 포함된 windef.h 에서 #ifndef max #define max(a,b) (((a) > (b)) ? (a) : (b)) #endif #ifndef min #define min(a,b) (((a) < (b)) ? (a) : (b)) #endif
<windows.h>의 min, max는 두 매개변수 중 작은 쪽이나 큰 쪽을 반환한다. 겉보기엔 쓸모 있어 보이지만 실제론 그렇지도 않다. C++ 표준 라이브러리에 동일한 역할을 하는 함수가 있기 때문이다.
std::min(1, 2); // 매개변수 중 작은 쪽을 반환한다. std::max(1, 2); // 매개변수 중 큰 쪽을 반환한다. std::numeric_limits<int>::min(); // int 타입의 최소값을 반환한다. std::numeric_limits<int>::max(); // int 타입의 최대값을 반환한다.
표준 라이브러리의 min, max는 인라인 템플릿 함수라서 매크로보다 성능이 떨어지지도 않고 매크로 때문에 발생하는 문제도 겪지 않는다. 그런데 <windows.h>에서 min과 max를 매크로로 선언했기 때문에 위의 코드는 컴파일되지 않는다.
매크로의 단점
#define max(a,b) (((a) > (b)) ? (a) : (b)) inline int max2(int a, int b) { return a > b ? a : b; } int a = 1; int b = 2; int retValue1 = max(a, ++b); int retValue2 = max2(a, ++b);변수 retValue1과 retValue2의 값은 무엇이 될까? 매크로 접해보지 않았다면 값이 똑같으리라 생각할지 모른다. 그러나 현실은 처참하다. retValue1은 4가 되고, retValue2는 3이 된다. max2(a, ++b)는 사실상 다음과 같이 변환되기 때문이다.
a > ++b ? a : ++b
warning C4003: \'max\' 매크로의 실제 매개 변수가 부족합니다.
이 문제를 해결하는 방법은 많다. 가장 쉬운 방법은 <windows.h>에서 min, max를 선언하지 않는 것이다. 그러려면 <windows.h>를 참조하기 전에 NOMINMAX를 선언하면 된다.
#define NOMINMAX // min, max 매크로 없애기 #include <windows.h>
이렇게 하면 min, max 매크로를 선언하지 않는다. 그러나 min, max 매크로를 그냥 두고 싶을지도 모른다. 그런 경우에 대비하여 <windows.h>에는 std::min과 std::max에 대응하는 _cpp_min, _cpp_max가 있다.
_cpp_min(a, b); _cpp_max(a, b);
편리한 방법이지만 해당 소스 파일을 다른 환경, 그러니까 GNU C++ 컴파일러를 써서 컴파일해야 할 때는 소용이 없다. 교차 플랫폼을 생각하면 환경에 따라 _cpp_min 매크로를 직접 선언해야 한다. 물론 윈도우 환경만 생각하면 가장 쉬운 해결책 중 하나다.
템플릿 타입을 명시함으로써 min, max 매크로의 치환을 막는 방법도 있다.
std::min<int>(a, b); std::max<int>(a, b);
std::min, std::max에 국한해선 이 방법이 좋다. 템플릿 함수이기 때문이다. 그러나 <windows.h>가 아니더라도 외부 라이브러리의 매크로 때문에 고생하는 일은 흔하다. 그 중 상당수는 템플릿 함수가 아니다. 만약 여러 상황에 두루 적용 가능한 수단이 필요하다면 다음의 사례를 고려해보자.
// 매크로 선언 무효화하기 #undef min #undef max // 함수를 ()로 감싸 매크로 치환 대상에서 제외하기 (std::min)(a, b); (std::max)(a, b); // 빈 매크로를 이용하기 #define BOOST_PREVENT_MACRO_SUBSTITUTION std::min BOOST_PREVENT_MACRO_SUBSTITUTION (a, b); std::max BOOST_PREVENT_MACRO_SUBSTITUTION (a, b);