항목 23 멤버 함수보다는 비멤버 비프렌드 함수와 더 가까워지자
23.1 멤버 함수 vs. 비멤버 비프렌드 함수
웹 브라우저를 나타내는 클래스가 있다고 가정해보자. 웹 브라우저로 다운로드한 파일을 저장한 캐시를 비우는 함수, 방문한 URL의 기록을 지우는 함수, 시스템이 갖고 있는 쿠키를 삭제하는 함수가 속해있다. 캡슐화 관점에서 봤을 때, 세 동작을 한꺼번에 호출하는 clearBrowserMember 멤버 함수와 clearBrowserNonMember 비멤버 함수 중 누가 더 좋은 걸까?
class WebBrowser {
public:
void clearCache();
void clearHistory();
void clearCookies();
void clearBrowserMember();
};
void WebBrowser::clearBrowserMember() { /* 멤버 함수로 세 동작을 한 번에 호출합니다. */
clearCache();
clearHistory();
clearCookies();
}
void clearBrowserNonMember(WebBrowser& wb) { /* 비멤버 함수로 세 동작을 한 번에 호출합니다. */
wb.clearCache();
wb.clearHistory();
wb.clearCookies();
}
멤버 버전인 clearBrowserMember은 비멤버 버전인 clearBrowserNonMember보다 캡슐화 정도에서 오히려 형편없다. 또한, 비멤버 함수를 사용하면 WebBrowser와 관련된 기능을 구성하는 데 컴파일 의존도를 낮추고 WebBrowser의 확장성을 높일 수 있다. 따라서 비멤버 함수가 멤버 함수보다 여러모로 낫다.
■ 23.1.1 캡슐화 관점에서 본 멤버 함수 vs. 비멤버 프렌드 함수
어떤 객체의 모습을 그 객체의 데이터로 설명할 수 있다고 생각해보자. 데이터를 직접 볼 수 있는, 다시 말해 직접 접근할 수 있는 코드가 적을수록 그 데이터는 많이 캡슐화된 것이고 그 객체가 가진 데이터(데이터 멤버의 개수, 타입 등)를 바꿀 수 있는 자유도가 그만큼 높아진다. 반면, 어떤 데이터를 접근하는 함수가 많으면 그 데이터의 캡슐화 정도는 낮다고 할 수 있다.
데이터 멤버가 private이면 여기에 접근할 수 있는 함수의 개수는 그 클래스의 멤버 함수의 개수와 프렌드 함수의 개수를 더하면 된다. 비멤버 비프렌드 함수는 어떤 클래스의 private 멤버에 접근할 수 있는 함수의 개수를 늘리지 않으므로 멤버 함수보다 캡슐화 정도가 훨씬 더 높다. 이것으로 clearBrowserNonMember 함수가 ClearBrowser 함수보다 더 바람직한 이유가 설명된다. WebBrowser 클래스에 대한 캡슐화의 정도가 높은 쪽을 고른 것이다.
23.2 편의 함수와 헤더 파일
C++는 이보다 더 자연스러운 방법을 구사할 수 있다. clearBrowserNonMember를 비멤버 함수로 두되, WebBrowserStuff와 같은 네임 스페이스(name space) 안에 두는 것이다. 네임 스페이스는 클래스와 달리 여러 개의 소스 파일에 나뉘어 흩어질 수 있다.
namespace WebBrowserStuff {
class WebBrowser { ... };
void clearBrowserNonMember(WebBrowser& wb);
}
clearBrowserNonMember 같은 함수는 사실 편의상 준비한 함수로 응용도가 높은 클래스는 이런 종류의 편의 함수가 꽤 많이 생긴다. 사실 일반적인 사용자는 여러 가지 편의 함수 중 일부에만 관심을 갖기 때문에 굳이 다른 편의 함수의 컴파일 의존성을 고민할 필요가 없다. 이것을 나누어 놓는 훌륭한 방법은 편의 함수마다 하나의 헤더 파일에 몰아서 선언하는 것이다.
/* "webbrowser.h" 헤더 */
namespace WebBrowserStuff { /* WebBrowser 클래스 자체에 대한 헤더입니다. */
class WebBrowser { ... };
... /* 거의 모든 사용자가 사용하는 함수가 여기 들어갑니다. */
}
/* "webbrowserbookmark.h" 헤더 */
namespace WebBrowserStuff {
... /* 북마크와 관련된 편의 함수가 여기 들어갑니다. */
}
/* "webbrowsercookies.h" 헤더 */
namespace WebBrowserStuff {
... /* 쿠키와 관련된 편의 함수가 여기 들어갑니다. */
}
이렇게 편의 함수 전체를 여러 개의 헤더 파일, 그러나 하나의 네임 스페이스에 나누어 놓으면 해당 네임 스페이스에 비멤버 비프렌드 함수를 추가함으로써 편의 함수 집합의 확장(extend)이 손쉬워진다. 반면, 클래스 멤버 함수는 반드시 클래스 전체가 전부 정의되어야 하기 때문에 이런 식으로 기능을 쪼개는 것 자체가 불가능하다.
한편, 표준 C++ 라이브러리가 이러한 구조로 되어 있다. 가령, std 네임 스페이스에 속한 기능과 함수들이 <iostream>, <vector>, <memory> 등 여러 개의 헤더에 나누어 선언되어 있다. 덕분에 <iostream> 기능만 필요한 사용자는 굳이 <memory>를 #include 할 필요가 없다.
NOTE 멤버 함수보다는 비멤버 비프렌드 함수를 자주 쓰도록 하자. 캡슐화 정도가 높아지고, 패키징 유연성도 커지며, 기능적인 확장성도 늘어난다.
'Object Oriented Programming(C++) > Effective C++' 카테고리의 다른 글
Effective C++ | 항목 26 변수 정의는 늦출 수 있는 데까지 늦추는 근성을 발휘하자 (0) | 2023.03.03 |
---|---|
Effective C++ | 항목 24 타입 변환이 모든 매개 변수에 적용되어야 한다면 비멤버 함수를 선언하자 (0) | 2022.02.15 |
Effective C++ | 항목 22 데이터 멤버가 선언될 곳을 private 영역임을 명심하자 (0) | 2022.02.11 |
Effective C++ | 항목 21 함수에서 객체를 반환할 경우에는 참조자를 반환하려고 하지 말자 (0) | 2022.02.03 |
Effective C++ | 항목 20 값에 의한 전달보다는 상수 객체 참조자에 의한 전달이 대개 낫다 (0) | 2022.02.03 |
댓글