Chapter 4. 설계 및 선언 part2 항목 22 - 25

Effecive C++ 정리

Posted by Kyung Jun Cha on 2019-11-08

Chapter 4. 설계 및 선언 part2 항목 22 - 25


항목 22. 데이터 멤버가 선언될 곳은 private 영역임을 명심하자.


  • 데이터 멤버는 private 멤법로 선언합시다.
  1. 일관성 있는 데이터 접근 통로 제공
    ex) 공개 인터페이스에 있는 것들이 전부 함수뿐이라면, 괄호 사용에 고민이 필요없다. length() size()
  2. 세밀한 접근 제어 가능 (캡슐화(encapsulation))
  3. 클래스의 불변속성 강화
  4. 내부 구현의 융통성 강화

pulbic은 캡슐화되지 않았다라는 뜻이며 '바꿀 수 없다’라는 의미를 담고 있습니다. public 멤버를 수정할 시 동반하여 수정해야하는 코드가 많기 때문입니다.
protected 또한 파생클래스를 수정해야 하기 때문에 힘들어 집니다.

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


  1. 캡슐화 정도가 높아집니다.
  • 인터페이스의 공개되는 영역의 크기로 캡슐화 정도를 측정합니다. 공개 함수 갯수가 줄어 영역이 줄어듭니다.
  • private 데이터를 직접 호출하지 않고 호출 함수를 통해 호출하기 때문입니다.
  1. 패키징 유연성도 커집니다.
    보통 이런 함수들은 같은 namespace를 활용하여 사용하는데 멤버 함수가 아닐경우 서로 다른 헤더 파일에 선언할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
//webbrowser.h //WebBrower 클래스 자체에 대한 헤더
//관련된 핵심 기능 선언
namespace WebBrowserStuff {
class WebBrower { ... };

void clearBrower {WebBrower& wb}; //비멤버 비프렌드 함수
...
}

//webbrowserbookmarks.h // 즐겨찾기 관련 기능
namespace WebBrowserStuff {
...
}
  1. 기능적인 확장성도 늘어납니다.

항목 24. 타입 변환이 모든 매개변수에 대해 적용되어야 한다면 비멤버 함수를 선언하자.


  • 어떤 함수에 들어가는 모든 매개변수에 대해 타입 변환을 해 줄 필요가 있다면, 그 함수는 비멤버이어야 합니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Rational { //실수를 나타내는 클래스
Rational(int numerator = 0, int denominator = 1); // 생성자에 암시적 변환을 허용하기 위해서 explicit 키워드는 사용하면 안됩니다.
...
}

const Rational operator*(const Rational& lhs, const Rational& rhs);

Rational oneFourth(1, 4);
Rational result;

result = oneFourth * 2;
result = 2 * oneFourth; // 멤버함수로 operator*를 선언할 경우
// 2.operator*(Rational)을 찾지 못하고 비멤버 함수를 검색하기 때문에
// 비멤버 함수가 없으면 에러가 발생합니다.

클래스의 공개 함수만으로 구현할 수 있는 함수는 굳이 friend함수로 하지 않는다.

항목 25. 예외를 던지지 않는 swap에 대한 지원도 생각해 보자.


  1. 표준에서 제공하는 swap이 납득할 만한 효율을 보이면 그냥 아무것도 하지 마십시오.
  2. 효율이 기대한 만큼 충분치 않다면 다음과 같이 하십시오. (ex) pimpl(pointer to implementation)와 유사한 경우)
  1. 효율적으로 맞바꾸는 함수를 public 멤버 함수로 두십시오
1
2
3
4
5
6
7
8
9
10
11
class Widget {
public:
...
void swap(Widget& other) //효율적인 함수
{
...
swap(pImpl, other.pImpl);
}
private:
WidgetImpl* pImpl; //Widget의 실제 데이터를 가진 객체에 대한 포인터
};
  1. 같은 네임스페이스 비멤버 swap을 만들어 넣습니다. 그리고 1번에서 만든 멤버 함수를 호출합니다.
1
2
3
4
5
6
namespace WidgetStuff {
void swap(Widget& a, Widget& b)
{
a.swap(b); //멤버 함수 호출
}
}
  1. 새로운 클래스 라면 std::swap의 특수화 버전을 준비해 둡니다.(클래스 템플릿일 경우 추가하지 않습니다.std에는 새로운 템플릿 추가를 금합니다.)
1
2
3
4
5
6
namespace std {
void swap(Widget& a, Widget& b)
{
a.swap(b);
}
}
  1. swap을 호출할 때, std::swap을 볼수 있도록 using 선언을 반드시 포합시킵니다.
1
2
3
4
5
6
7
8
9
10
template<typename T>
void doSomething(T& obj1, T& obj2)
{
using std::swap;
...
swap(obj1, obj2); // 1. T와 동일한 네임스페이스 안에 T 전용의 swap을 찾습니다.
// 2. std::swap의 T 특수화 버전을 찾습니다.
// 3. std::swap의 일반형 템플릿 함수를 호출합니다.
...
}