저번 항목에 이어서, 이번 항목은 리팩토링이다. 때 마침 리팩토링 관련 서적을 읽고 있던 터라, 그 필요성을 알게 되었었터라 "무엇을 어떻게 리팩토링 할까?" 에 대해서 생각해 보게 된다.

1 ) std::string의 멤버 함수들 중 멤버가 아니면 안되는 것들은 무엇이 있고, 왜 그런가?

멤버가 아니면 안되는 함수들은, 생성자, 소멸자, 할당 연산자, operator[] 연산자들이 있다. 이것은 멤버가 아니면, 어찌 할 수 없는 것들이다.

왜 생성자와 소멸자, operator=과, operator[] 연산자들은 왜 멤버로 놔야 할까?

여기서 중요한 개념을 짚어 봐야 한다. 스콧 마이어스는 "멤버로도 구현할 수 있고, 비친구 비멤버로도 구현 할 수 있는 함수가 있다면, 비친구 비멤버를 선호 해야 한다" 로 말했다.

이 이야기의 대표적인 사례는 바로 "algorithm header"에 있다. 만약 순차열 검색을 컨테이너 멤버 함수로 지정해 두었다면, 다른 컨테이너들은 그 구현을 다시 만들어야 할 것이다. 이것(멤버로 둔다는 것)은 재사용에 큰 제약을 걸게 되는 꼴이 된다.

그러니, 멤버로 두어야 할지 비멤버로 두어야 할지가 중요한 것이다.

그럼에도 불과하고, 멤버로 놔야 할 것들이 있는데, C++ 언어의 규칙상 반드시 멤버가 되어야할 연산자들이 있다. 그것이 바로 생성자와 소멸자, 그리고 각 operator 들이 있다.

여기서 멤버와 비멤버로 두어야 할 분류 규칙을 알아보자.

  • 반드시 멤버가 되어야 할 것은 멤버로 한다.
  • 클래스 내부에 접근할 필요가 있다면, 멤버로 하는 것을 선호한다.
  • 그 외의 모든 경우들에서는 비멤버 비친구 함수로 만드는 것을 선호한다.

즉, 비멤버 비친구로 쉽게 할 수 있다면, 이것을 우선시하고, 이럴 수 없다면, 멤버로 두는 것이 좋다.

"생성자, 소멸자, operator=, operator[]"는 C++ 언어의 규칙과 클래스 내부에 접근할 필요성이 있기에, 멤버로 하는 하는 것이 좋다.(경험상 경우에 따라, 친구 비멤버 함수로 해야 할 경우도 더러 있더라...)


2 ) std::string의 멤버 함수들 중 멤버가 되는 게 좋은 것들은 무엇이고, 왜 그런가?

우선 함수들은 begin(), end(), rbegin(), rend(), size(), max_size(), capacity(), reserve(), swap(), c_str(), data(), get_allocator(), insert(), erase(), replace() 들이 있다. 이 것들은 아주 밀접하게 내부 데이터와 연결되어 있기 때문이다. 물론 이것들은 non-member friend function 으로 만들 수 있지만, 좀 귀찮아지고 보기도 껄끄러워 지기에, 멤버로 두는것이 더 좋을 것이다. 즉, 모든 조건이 동등할 경우, 가장 단순한 것을 택하는게 제일 좋다는 것이다.


3 ) std::string의 멤버 함수 at, clear, empty, length를 비멤버 비친구 함수로 제공해도 일반성이나 유용성이 손실되지 않으며, std::string의 나머지 인터페이스에 어떠한 영향도 미치지 않는 이유에 예를 들어라.

책의 예를 코드로 쓰자니 너무 귀찮다. : )

이 함수들은 non-member non-friend function 으로 구현 한다면, 다음의 이점을 갖게 된다.

1. 단순함, 멤버 함수가 적으면 적을 수록 클래스는 더 단순해 질 것이다.

2. 호환성, 통합된 인터페이스만 사용 하게 되면, 호환 알고리즘 작성이 더 쉽게 될 것이다.

3. 캡슐화, 내부를 더 적게 건들이니, 캡슐화가 향산 된다.

4. 이름공간 오염, : 이건 장단점도 아닌데, 이런 반박이 있기에 허브셔터가 쓴 것으로 보인다.

5. 일관성, 마이어스가 말했던 데로 비멤버, 멤버를 구분지어 만들었다면, 일관성을 갖게 된다.


총평

음~ 뭐 더 좋을 수 있다는 이야기이지, 그렇게 해야만 한다라는 이야기가 아니므로, 크게 연연해하지 말자.

신고
posted by 농사를 짓는 게임 프로그래머 최익필

댓글을 달아 주세요