C++ interface, Impl 이디엄
인터페이스 클래스와 구현 클래스
클래스는 c++의 기본 추상화 단위다. 클래스를 작성할 떄는 추상화 원칙을 적용하여 인터페이스와 구현을 최대한 분리하는 것이 좋다. 특히 데이터 멤버를 모두 private로 지정하고 getter와 setter를 제공하여 내부를 보호하는 것이 바람직하다.C++에서는 public 인터페이스와 private (또는 protected) 데이터 멤버 및 메서드를 모두 클래스 정의에 작성하기 때문에 클래스의 내부 구현사항이 클라이언트에 어느정도 노출됦 수밖에 없다. 이러한 점으로 인해 non-public 메서드나 데이터 멤버를 클래스에 추가할 때마다 이 클래스를 사용하는 클라이언트를 매번 다시 컴파일해야 한다. 프로젝트가 거대해 질수록 이 작엡에 대한 부담이 커진다.
다행이 인터페이스를 보다 간결하게 구성하고 구현 세부사항을 모두 숨겨서 인터페이스를 안정적으로 유지하는 방법이 있다. 처음에 접할땐 생소하고 작성할 코드가 늘어나는 점이 있지만 기본 원리는 같기 때문에 알아두면 아주 좋다. 기본 원칙은 작성할 클래스마다 인터페이스 클래스와 구현 클래스를 따로 정의하는 것이다. 구현 클래스는 여기서 설명하는 원칙을 적용하지 않을 때 흔히 작성하는 클래스를 말한다. 인터페이스 클래스는 구현 클래스와 똑같이 public 메서드를 제공하되 구현 클래스 객체에 대한 포인터를 갖는 데이터 멤버 하나만 정의한다. 이를 핌플 이디엄 (pimpl idiom, private implementation idiom), 핌플 구문 또는 브릿지 패턴 (bridge pattern) 이라 부른다.
그러면 구현 코드가 변해도 public 메서드로 구성된 인터페이스 클래스는 영향을 받지 않는다. 따라서 다시 컴피일할 일이 줄어든다. 이렇게 정의된 인터페이스 클래스를 사용하는 클라이언트는 구현 코드만 변경됐다면 소스를 다시 컴파일할 필요가 없다. 여기서 주의할 점은 인터페이스 클래스에 존재하는 유일한 데이터 멤버를 구현 클래스에 대한 포인터로 정의해야 제대로 효과를 발휘한다는 것이다. 데이터 멤버가 포인터가 아닌 값 타입이면 구현 클래스가 변경될 때마다 다시 컴파일 해야한다.
인터페이스와 구현을 확실히 나누면 엄청난 효과를 얻을 수 있다. 처음에는 좀 버거롭지만 일단 익숙해지면 자연스럽게 작성할 수 있다. 가장 큰 장점은 인터페이스와 구현을 분리하면 단순히 스타일 측면만 좋아지는 것이 아니라 구현 클래스를 변경할 일이 많아져도 빌드 시간을 절약할 수 있다는 것이다. 핌플 이디엄을 적용하지 않고 클래스를 작성하면 구현 코드가 조금이라도 변경되면 빌드 시간이 길어질 수 있다. 예를 들어 클래스 정의에 데이터 멤버를 새로 추가하면 이 클래스 정의를 include 하는 모든 소스 파일을 다시 블드해야 한다. 반면 핌플 이디엄을 적용하면 인터페이스 클래스를 건드리지 않는 한 구현 클래스의 코드를 변경해도 빌드 시간에 영향을 받지 않는다.
Example
여기서 중요한 점은 cpp파일의 include는 오직 해당 cpp파일에만 영향을 미친다는 것이다. 따라서 위의 코드는 헤더파일의 include를 구현파일로 모두 보내버렸다는 것이 포인트다. 또한 스마트 포인터로 설계를 한다면 delete를 하지 않아도 되기 때문에 더 깔끔한 코드를 작성할 수 있다.
참고
- 전문가를 위한 C++
- example