상세 컨텐츠

본문 제목

항목 23) 멤버 함수보다는 비멤버 비프렌드 함수를 사용하자.

C++/Effective C++

by DeaKyungLee 2022. 8. 27. 17:18

본문

 

웹 브라우저를 나타내는 클래스가 하나 있다고 가정해보자.
해당 클래스에는 다운로드 임시저장 캐시를 비우는 기능, 방문 URL 기록을 비우는 기능, 쿠키를 제거하는 기능 등이 있을 것이다.

class WebBrowser {
public:
    ...
    void clearCache();
    void clearHistory();
    void removeCookies();
    ...
};


물론 이러한 동작들을 하나의 함수를 통해 한 번에 처리하는 것도 가능하다.

class WebBrowser {
public:
...
    void clearEverything(); // calls clearCache, clearHistory,
                            // and removeCookies
...
};


위와 같이 멤버 함수로 구현하는 것과

void clearBrowser(WebBrowser& wb)
{
    wb.clearCache();
    wb.clearHistory();
    wb.removeCookies();
}

 

이런 식으로 비멤버 함수로 제공해서,
객체의 멤버 함수를 순서대로 호출하기만 하는 방식으로 구현할 수도 있을 것이다.

 

-> 위의 두 가지 방식 중에 뭐가 더 나은 방식일까?

이번 장의 핵심은 바로 이 질문이다.
멤버 함수? 비멤버 함수?

 

결론부터 말하자면 대부분의 경우, 후자의 경우가 더 나은 선택이라고 할 수 있다.
이유는 항목 22) 에서 언급한 것과 비슷한데, 멤버 함수는 캡슐화의 정도를 낮추기 때문이다.

캡슐화를 한다는 것은 외부에서 볼 수 없다는 의미이다.
그리고 외부에서 볼 수 없다는 것은 그 만큼 유연성이 늘어난다는 것을 의미한다.
변경이 필요한 상황에서 해당 변경이 영향을 주는 것은 '변경된 것을 볼 수 있는 것들'로 한정할 수 있기 때문이다.
즉, 캡슐화가 될 수록 변경 시의 유연성 역시 커진다는 것을 의미한다.

private 데이터 멤버라도
해당 데이터에 접근할 수 있는 함수가 많다는 것은 그 데이터의 캡슐화 정도가 낮다는 이야기이다.
왜냐하면 private 멤버에 접근할 수 있는 것은 그 클래스의 멤버 함수 및 프렌드 함수로만 한정될 수 있기 때문이다.
바꿔서 말하자면, 비멤버 비프렌드 함수는 결코 private 멤버에 접근할 수 없기 때문에 캡슐화를 낮추지 않는다.
이러한 이유로 비멤버 비프렌드 함수가 멤버 함수보다 더 바람직하다고 말할 수 있는 것이다.

그리고 C++ 에서는 더 자연스러운 방법이 존재하는데, 바로 namespace 라는 기능을 활용하는 것이다.
위의 예시 상황에서는 

namespace WebBrowserStuff {
 
 
class WebBrowser { ... };
 
 
void clearBrowser(WebBrowser& wb);
 
...
 
 
}


이런 식으로 같은 네임스페이스 내부에 clearBrowser 함수를 선언해주는 것이다.
그리고 namespace 는 서로 다른 소스 파일에 여러 부분으로 나뉘어 흩어질 수 있다는 중요한 특성이 존재한다.
이것은 특히나 clearBrowser 같은 편의성 함수에 대해서 중요한 부분이다.
단순히 clearCache, clearHistory, removeCookies 함수를 순서대로 호출하는 비멤버 비프렌드 함수이기 때문에
WebBrower 클래스 사용자 수준에서 얻어낼 수 없는 기능은 이들도 제공할 수 없다.

이런 편의성 함수는 WebBrowser 같은 클래스에서 여러가지 형식으로 등장할 수 있다.
( 즐겨찾기, 프린터 관련, 쿠키 관리용 함수 등 )
이러한 기능들을 각각 다른 헤더 파일에 모아서 선언하는 방식을 가능하게 해주는 것이 namespace 인 것이다.

// header "webbrowser.h" — header for class WebBrowser itself
// as well as "core" WebBrowser-related functionality
namespace WebBrowserStuff {
 
    class WebBrowser { ... };
    ...                     // "core" related functionality, e.g.
                            // non-member functions almost
                            // all clients need
}
 
// header "webbrowserbookmarks.h"
namespace WebBrowserStuff {
 
    ...                     // bookmark-related convenience
}                           // functions
 
// header "webbrowsercookies.h"
namespace WebBrowserStuff {
 
    ...                     // cookie-related convenience
}                           // functions

 

예를 들자면 즐겨찾기 관련 기능만 필요한 사용자는
프린터나 쿠키 관리 기능에 대해서는 관심이 없기 때문에 굳이 관련 함수들을 불러들일 이유가 없다.
자신이 필요한 즐겨찾기 기능이 있는 헤더만 불러오면 되는 것이다.

사실 이러한 방식으로 구현된 것이 표준 C++ 라이브러리이다.
std 네임스페이스에 속한 모든 것들은 하나의 헤더에 모여있는 것이 아니라,
수십개의 헤더( vector, algorithm, memory 등 ) 에 흩어져서 선언되어 있다.

클래스 멤버 함수라면 이러한 방식으로 기능을 쪼개는 것이 불가능하다.
하나의 클래스는 그 전체가 통째로 정의되어야 하고 여러 조각으로 나눠서 구현이 불가능하기 때문이다.
여러 헤더 파일에 나눠서 하나의 네임스페이스를 구성하는 방식은 확장을 하는 상황에서도 유리하다.
단순히 헤더 파일을 하나 만들고 해당 네임스페이스를 만들어서 관련 기능을 구현하기만 하면 되기 때문이다.

상속이라는 기능을 통해 파생 클래스로 비슷하게 흉내낼 수 있지 않을까 생각할 수 있지만,
파생 클래스는 기본 클래스의 private 멤버에 접근할 수 없다.
그리고 모든 클래스가 기본 클래스로 쓸 용도로 설계되지는 않는다.

 

Things to Remember

  • 멤버 함수보다는 비멤버 비프렌드 함수를 더 지향하자. 캡슐화 정도가 높아지고, 패키징 유연성, 기증적 확장성도 늘어난다.
  • 가능하다면 편의성 비멤버 비프렌드 함수는 서로 다른 헤더, 같은 네임스페이스에 선언해서 관리하자.

관련글 더보기

댓글 영역