-
Effective C++...29일지 2022. 7. 26. 21:09
파일 사이의 컴파일 의존성을 최대로 줄이자
컴파일 의존성이란
하나의 클래스가 수정되었을 때 그 클래스를 사용하는 클래스가 함께 컴파일되어야 하는 경우 컴파일 의존성이 있다고 한다.
다음 Person 클래스에서 사용된 변수의 클래스가 수정된다면 Person 뿐만 아니라 Person을 사용하는 모든 클래스도 함께 다시 컴파일되어야 한다.
class Person {
public:
Person(const std::string& name, const Date& birthday, const Address& addr);
std::string name() const;
std::string birthDate() const;
std::string address() const;
...
private:
std::string theName;
Date theBirthDate;
Address theAddress;
};
※ 표준 라이브러리 헤더는 대부분의 경우 컴파일 시 병목 요인이 되지 않는다.
컴파일 의존성을 피하는 방법
다음의 두 가지 방법이 존재한다.
- 핸들 클래스 헤더를 선언을 위한 헤더와 구현을 위한 헤더로 분리하는 방법.
- 인터페이스 클래스 순수 가상 함수를 만들어 인터페이스로 사용하는 방법.
먼저 핸들 클래스의 경우 다음과 같이 선언을 Person.h에 두고 실제 구현은 PersonImpl.h에 두어 Person.h의 함수에서 PersonImpl.h의 함수를 호출하도록 한다.
std::string Person::name() const
{
// 헤더의 함수와 실제 구현의 함수는 같은 이름을 사용하여 유지 보수가 쉽도록 한다.
return pImple->name();
}
인터페이스를 사용하는 경우 가상 클래스를 만들고 그 클래스를 상속받는 클래스를 구현하는 식으로 사용한다.
class Person {
public:
virtual ~Person();
virtual std::string name() const = 0;
virtual std::string birthDate() const = 0;
virtual std::string address() const = 0;
...
};
class RealPerson : public Person {
Person(const std::string& name, const Date& birthday, const Address& addr)
: theName(name), theBirthDate(birthday), theAddress(addr) { }
virtual ~RealPerson() { }
std::string name() const;
std::string birthDate() const;
std::string address() const;
...
private:
std::string theName;
Date theBirthDate;
Address theAddress;
};
여기에서 사용자가 RealPerson을 사용하는 대신 Person의 인터페이스로 호출할 수 있도록 Person에서 RealPerson을 생성해서 돌려주는 함수를 만들어줘야 할 것이다.
class Person {
...
// 함수 구현부에서 RealPerson을 초기화해 반환해주면 된다.
static std::tr1::shared_ptr<Person> create(const std::string& name, const Date& birthday,
const Address& addr);
...
};
선언과 구현을 분리할 때의 단점
두 가지 방법은 컴파일 의존성을 완화시킬 수 있지만 각각 단점이 존재한다.
먼저, 핸들 클래스의 경우 접근할 때 요구되는 간접화 연산이 한 단계 증가하고 객체 하나를 저장하는데 필요한 메모리 크기에 구현부 포인터 크기가 추가로 필요하게 된다.
인터페이스 클래스의 경우 함수 호출마다 가상 테이플 점프 비용이 소모되고 인터페이스 클래스의 파생 클래스는 모두 가상 테이블 포인터를 가지고 있어야 한다.
또한 두 방식 모두 인라인 함수를 적절하게 사용할 수 없게 된다는 단점이 존재한다.