항목 31. 파일 사이의 컴파일 의존성을 최대로 줄이자
C++
는 인터페이스와 구현을 깔끔하게 분리하는 일에 별로 일가견이 없다.
#include " date.h"
#include " address.h"
#include < string>
class Person {
public:
std::string address () const ;
std::string birthDate () const ;
std::string name () const ;
private:
Date theBirthDate; // 구현 세부사항
Address theAddress; // 구현 세부사항
std::string theName; // 구현 세부사항
}
#include
문은 Person
을 정의한 파일과 헤더 파일들 사이에 컴파일 의존성을 엮어버린다.
헤더 파일 중 하나라도 바뀌거나 이들과 엮여 있는 헤더파일이 바뀌기만 해도 Person
클래스를 정의한 파일은 다시 컴파일 된다.
컴파일러는 정의문을 만나면 객체를 담을 공간을 할당한다.
int x;
Person p{params}; // 1
Person *p; // 2
Person 객체 하나의 크기를 알려면 정의부 를 봐야 한다.
Person 객체의 포인터를 담을 공간 만 할당하면 된다.
Java
의 경우 해당 방식으로 이루어져 있다.
C++
도 포인터 뒤에 실제 객체 구현부를 숨길 수 있다.
정의부에 대한 의존성을 선언부에 대한 의존성으로 바꾸자
객체 참조자 및 포인터로 충분한 경우에는 객체를 직접 쓰지 않는다.
어떤 타입에 대한 참조자 및 포인터를 정의할 때는 그 타입의 선언부 만 필요하다.
어떤 타입의 객체를 정의할 때는 그 타입의 정의 가 준비되어 있어야 한다.
클래스 정의 대신 클래스 선언에 최대한 의존하도록 만든다.
어떤 클래스를 사용하는 함수를 선언할 때는 그 클래스의 정의를 가져오지 않아도 된다.
클래스 객체를 값으로 전달하거나 반환하더라도 클래스 정의가 필요없다.
class Date ; // 클래스 선언
Date today ();
void clearAppointments (Date d); // Date 클래스의 정의를 가져오지 않아도 된다.
선언부와 정의부에 대해 별도의 헤더 파일을 제공한다.
선언부를 위한 헤더 파일과 정의부를 위한 헤더파일로 나눈다.
라이브러리 사용자 쪽에서는 전방 선언 대신에 선언부 헤더 파일을 #include
한다.
라이브러리 제작자 쪽에서는 헤더 파일 두 개를 짝지어 제공해야 한다.
#include " datefwd.h" // Date 클래스를 선언하고 있는(정의하진 않는) 헤더 파일
Date today ();
void clearAppointments (Date d);
핸들 클래스 와 인터페이스 클래스 를 통해 구현부로부터 인터페이스를 떼어 놓아 파일들 사이의 컴파일 의존성을 완화시킬 수 있다.
pimpl
관용구를 사용하는 클래스를 핸들 클래스라고 한다.
핸들 클래스의 멤버 함수를 호출하면 구현부 객체의 데이터까지 가기 위해 포인터를 타야 한다.
한번 접근할 떄마다 요구되는 간접화 연산이 한 단계 더 증가한다.
객체 하나씩을 저장하는데 필요한 메모리 크기에 구현부 포인터의 크기가 더해지는것도 필수이다.
구현부 포인터가 동적 할당된 구현부 객체를 가리키도록 구현부 포인터의 초기화가 일어나야 한다.
인라인 함수의 도움을 제대로 끌어내기 힘들다.