본문 바로가기

Effective C++/4. 설계 및 선언

항목 23: 함수보다는 비멤버 비프렌드 함수와 더 가까워지자

웹브라우저를 나타내는 클래스가 하나 있다고 가정해보자. 웹브라우저면 많은 함수를 지원할 것인데, 캐시를 비우는 함수, 방문기록을 없애는 함수, 쿠키를 없애는 함수 등등… 중에는 모든 것을 한꺼번에 지원하는 함수도 있을 것이다.

 

 

clearEverything() clearBrowser() 누가 괜찮을까?

정답을 먼저 알려주자면 비멤버함수쪽이 낫다.

 

===================================================

 

객체 지향 법칙중에 이런 이야기가 있다. 데이터와 데이터를 기반으로 동작하는 함수는 묶여 있어야 하며 => 대목 때문에 멤버 함수가 낫다고 얘기한다. 객체 지향이 있는 만큼 데이터를 캡슐화하라고 주장하고 있지만, 멤버함수 버전 clearEverything() 보다 비멤버함수 버전이 캡슐화가 되어 있는 경우이다.

아니라, 비멤버 함수를 사용하면, WebBrowser 관련 기능을 구성하는 있어서 패키징 유연성(pakage fexibility) 높아지고, 이로 인해 컴파일 의존도를 낮추고, WebBrowser 확정성을 높일 있다.

 

==================================================

 

??? 일단 캡슐화가 높은지 부터 알아보자.

 

어떤 것을 캡슐화하면 우선 외부에서 이것을 볼 수 없게 된다 -> 캡슐화 정도가 늘어나면 그만큼 밖에서 수있는 것들이 줄어든다. -> 무언가를 바꿀 필요한 유연성이 커지게 된다.  == 어짜피 캡슐화 되있는 것들은 바꿔도 밖에서 안보임 => 바꿀 있는 여유가 많아짐 => 유연성이 커지게

 

됐고 비멤버 버전이 캡슐화가 높은데?

어떤 객체의 상태를 객체의 데이터로 설명할 있다고 생각해보면, 데이터를 직접 있는(접근할 있는) 코드(대표적으로 멤버함수) 적으면 적을수록 데이터는 캡슐화 정도가 높다는 것이다. 같은 클래스의 맴버라고 해도 어쨌든 공개가 많이 되는 것이기 때문이다.

 

 

항목 22에서 데이터 맴버는 private 맴버어야 한다고 했는데, 데이터에 접근 있는 함수의 개수를 프로그램의 손으로 제한 있기 때문이다. public이면 당연히 어떻게 제어를 못한다. private이면 데이터에 접근할 있는 함수의 개수를 예측할 있다. 클래스의 맴버함수 + friend 맴버 함수의 개수를 더하면 된다.

 

이제 다시 비멤버 비프렌드 함수(어느것도 접근할 없는 함수) vs 멤버함수(private 데이터 멤버, 다른 private 함수, 나열자, typedef 타입 등등 모두 접근 가능) 생각해보자. 캡슐화 정도를 생각해보면 전자가 당연히 높다. 왜냐하면 비멤버 비프렌드 함수는 private 멤버에 접근할 있는 함수의 개수를 늘리지 않는다.

 

여기서 주의해야

  1. 비멤버 비프렌드(non-friend)함수에만 적용되는 이야기이다.
  1. 캡슐화에 대한 이런저런 이야기 때문에 "함수는 어떤 클래스의 비멤버가 되어야 한다" 라는 주장이 " 함수는 다른 클래스의 멤버가 없다" 라는 의미가 아니다.

2번의 경우에 함수를 어떤 클래스안에 무조건 넣어야 하는 언어들도 있고(java, c# …), 이를테면 clearBrowser() 다른 유틸리티 클래스의 정적 멤버 함수(this 없으니 == 원래 비멤버 함수이니) 넣어도 된다는 이야기이다. 어쨌든 clearBrowser() WebBrowser 멤버가 아니기만 하면 위의 이야기가 똑같이 적용된다는 이야기임. 어쨌든 WebBrowser 멤버가 아니니 WebBrowser 클래스의 관점에서 보면 비멤버함수이다.

 

 

C++에선 이런 상황을 좋게 바꿀 있는 방법이 있다. clearBrowser() 비멤버 함수로 두되, WebBrowserStuff 같은 네임스페이스 안에 두는 것이다.

 

 

 

네임스페이스는 클래스와 달리 여러 개의 소스 파일에 나뉘어(서로 다른 번역 단위에 나뉘어) 흩어질 있기 때문이다.

 

이렇게 하면 장점이 많은데, WebBrowser같은 활용도가 높은 클래스는 ClearBrowser() 같은 편의 함수(편의상 준비한 함수)들이 많이 생길 있다. 인쇄에 관련된 함수, 즐겨찾기에 관련된 함수, 쿠키에 관련된 함수 등등.. 일반적인 경우, 웬만한 사용자라면 이들 편의 함수들중 몇몇개만 알고 사용할 것이다. 즐겨찾기 관련 기능을 만드는 사람이 쿠키 관련 편의 함수에 대한 컴파일 의존성을 고민할 필요가 없다는 것이다.

 

그럼 편의 함수들을 나눠서 즐겨찾기 기능 따로, 인쇄 기능 따로 이렇게 못나누나?

=> 즐겨찾기 관련 함수들을 헤더파일에 몰아넣고, 인쇄 관련 함수들을 다른 헤더파일에 몰아놓고 이런식으로 하는 것이다.

 

 

 

C++ 표준 라이브러리도 이런 구조로 구성되어 있다. std::namespace 속한 모든 것이 <C++StandardLibrary> 헤더 같은 것에 모조리 들어가 있는 것이 아니라, 몇개의 관련된 기능이 있는 함수들이 수십 개의 헤더(<vector>, <algorithm> 등등) 흩어져 선언되어있다. vector 기능만 필요한 사용자는 <vector> #include 하면 되고, list 사용하지 않는 사용자면 굳이 <list> #include 필요가 없다.

 

=> 반면 클래스 맴버 함수로 오게 되면 이런거 자체가 불가능함. 하나의 클래스는 전체가 통으로 정의되어야 하고 여러 조각을 나눌 수가 없기 때문이다.

 

============================================================

 

편의 함수 전체를 여러 개의 헤더 파일에(하지만 하나의 네임스페이스에) 나누어 놓는 방식 편의 함수 집합(헤더파일) 확장(extend) 손쉬워 진다. 해당 네임스페이스에 비멤버-비프렌드 함수를 원하는 만큼 추가해 주기만 하면 그게 확장이다. 예를 들어, WebBrowser 쓰고 있던 사용자가 어찌하다 다운로드 이미지에 관련된 편의 함수를 만들어야 하겠다고 하면, 헤더 파일을 새로 하나 만든 후에 WebBrowserStuff 네임스페이스를 만들고 안에 관련 함수만 끼워 넣으면 끝이라는 것이다.

 

클래스로는 이러한 형태가 안되고 상속으로 부분적으로 가능한데, derived class base class private 멤버에 접근할 없기 때문에 부분적으로만 가능하다는 것이다. 그리고 항목 7 보면 모든 클래스가 기본 클래스로 목적으로 설계된 것이 아니기 때문에, 이런 방식은 힘든 것이다.

 

===========================================================

 

  • 맴버 함수보단 비맴버-비프랜드 함수를 자주 쓰도록 하자. 캡슐화 정도가 높아지고, 패키징 유연성도 커지며, 기능적 확장성도 늘어난다.