10.6 헤더 파일
헤더 파일은 서브 시스템이나 다른 코드에 추상화된 인터페이스를 전달하는 메커니즘이다. 한 가지 까다로운 부분은 여러 헤더 파일을 이용할 때 순환 참조나 중복 인클루드되는 경우를 피하는 것이다. 예를 들어 Logger클래스를 작성해서 모든 오류 메시지를 로깅해야 할 때, 사용자 설정 정보를 받기 위해 Preferences클래스를 사용해야 할 수 있다. 그런데 Preferences클래스가 사용하는 헤더 파일이 의도하지 않게 Logger클래스를 인클루드해버릴 수도 있다.
다음 코드에서 볼 수 있듯이 #ifndef 패턴을 이용하면 순환참조나 중복 인클루드를 막을 수 있다. 모든 헤더파일의 시작부분에 #ifndef 지시자를 넣어서 특정 키워드가 정의되지 않았다는 것을 확인한다. 만약 해당 키워드가 정의되어 있지 않다면 정상적으로 코드를 인클루드 한다. 이러한 매커니즘을 인클루드 가드(include guards) 라고 한다.
#ifndef LOGGER_H
#defind LOGGER_H
#include "Preferences.h"
class Logger
{
public:
static void setPreferences(cosnt Preferences& prefs);
static void logError(const char* error);
};
#endif // LOGGER_H
만약 사용하는 컴파일러가 #pragma once 지시자를 지원한다면 같은 매커니즘을 다음처럼 더 간단하게 구현할 수 있다. (#pragma once는 마이크로 소프트 비주얼 C++와 GCC에서 지원한다).
#pragma once
#include "Preferences.h"
class Logger
{
public:
static void setPreferences(const Preferences& prefs);
static void logError(const char* error);
};
인클루드 가드(#ifndef ... #endif 메커니즘) 나 #pragma once 같은 지시자는 헤더파일이 중복 인클루드되는 것 뿐만 아니라 심벌이 중복 정의되는 것도 막아준다. 예를 들어 A.h가 Logger.h를 인클루드하고 B.h도 Logger.h를 인클루드한다고 하자. 만약 소스 파일 App.cpp에서 두 헤더 파일 A,h와 B.h를 모두 인클루드하더라도 Logger.h자체에 중복 인클루드를 막는 장치가 되어 있기 때문에 Logger클래스의중복 정의로 인한 컴파일 에러가 발생하지 않는다.
순환 참조에 대응하기 위해 포워드 선언이라는 기능도 제공된다. 어떤 클래스를 작성할 떄 작성중인 클래스를 광범위하게 참조하는 다른 클래스를 참조해야 할 때가 있다. 그런데 그 클래스가 정의된 헤더파일을 인클루드하게 되면 그 헤더 파일에서 참조하고 있는 자기 자신도 인클루드 하게 되어 정상적으로 컴파일될 수 없다.
이때는 참조하는 클래스가 있다는 사실만 컴파일러에 알려주고 상세한 정의 부분을 상황에따라 생략할 수 있다. 당연하지만 정의부를 컴파일러에 알려주지 않았기 때문에 해당 소스에서 문제의 클래스를 직접 이용할 수 없다. 대신 나중에 해당 이름으로 클래스 정의를 찾아서 링크할 수 는 있다. 그리고 이렇게 되려면 참조하는 클래스의 객체 자체를 선언해서는 안되고 그 포인터나 참조를 사용해야 한다.
다음 코드는 Logger클래스에서 Preferneces클래스를 포워드 선언을 통해 참조하되 그 정의가 있는 헤더 파일은 인클루드 하지 않는다.
#ifndef LOGGER_H
#define LOGGER_H
class Preferences; // 포워드 선언
class Logger
{
public:
static void setPreferences(const Preferences& prefs);
static void logError(const char* error);
};
#endif // LOGGER_H
다른 헤더 파일을 인클루드하는 대신 최대한 포워드 선언을 이용하는 것이 바람직하다. 그렇게 하면 헤더 파일 간의 종속성이 줄어들기 때문에 초기 컴파일은 물론 재컴파일 시간도 줄어든다. 당연하지만 코드의 구현부에서는 포워드 선언을 했던 타입에 대해 직접 헤더파일을 인클루드 해야한다. 그렇지 않으면 컴파일되지 않는다.
'전문가를 위한 C++정리' 카테고리의 다른 글
10. C++의 까다롭고 유별난 부분들 10.5 C++11 / C++14 (0) | 2024.03.14 |
---|---|
10. C++의 까다롭고 유별난 부분들 10.4 스코프 지정 (0) | 2024.03.11 |
10. C++의 까다롭고 유별난 부분들 10.3 타입과 캐스팅 (0) | 2024.03.08 |
10. C++의 까다롭고 유별난 부분들 10.2 키워드 혼동 (0) | 2024.03.07 |
10. C++의 까다롭고 유별난 부분들 10.1 참조형 (0) | 2024.03.05 |