본문 바로가기
프로그래밍언어/C++

[Effective C++] 항목 22 ~ 25

by 목가 2018. 1. 23.
반응형

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


캡슐화(encapsulation)

class SpeedDataCollection
{
public:
void addvalue(int speed);      // 속도값 수집
double averageSoFar() const; // 평균값 반환
...

}


<평균값을 구하는 방법>
첫 번째 방법 : 지금까지 수집한 속도 데이터 전체의 평균값을 담는 데이터 멤버를 클래스 안에 넣어 둠.
 averageSoFar() 호출될 때마다 멤버의 값을 반환하기만 하면 끝.
두 번째 방법 :  averageSoFar() 호출할 때마다 평균값 계산.

정답은 없다 : 메모리 불충분, 평균값 그다지 필요하지 않은 환경 -> 두 번째
              메모리가 충분하며 평균값을 빈번히 사용하고 속도가 중요 -> 첫 번째

<캡슐화 사용 이유>
 접근 제한 가능,
 public 함수 내부구현 바꿈으로서 구현 변경 쉬움
 데이터 멤버 읽기/쓰기 시 다른 객체에 알림
 클래스 - 불변속성(private data), 사전/사후 조건 검증(set/get 함수에서)
 스레딩 환경 - 동기화

캡슐화 = 현재 구현을 나중에 바꾸기로 결정하는 권한 예약
- public, protected 함수 = 캡슐화되지 않았다 = 바꿀 수 없다 (널리 쓰일 수록)
- public, protected 함수를 변경할 경우 의존성 코드들 전부가 수정되고 다시 테스트

잊지 말자
- 데이터 멤버는 private으로 선언.
 왜? 문법적 일관성있는 데이터 접근 통로, 세밀 접근 제어, 클래스 불변속성, 융통성
- public = protected


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

웹브라우저를 나타내는 클래스.
class webBrowser
{
public:
void clearCache();
void clearHistory();
void removeCookies();
}

세 함수를 동시 호출하는 함수 : 멤버 함수 VS 비멤버 비프렌드 함수
class webBrowser
{
public:
void clearCache();
void clearHistory();
void removeCookies();
...
void clearEverything();
}

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

객체지향의 법칙 - 캡슐화 측면 : 비멤버 비프렌드 함수

캡슐화 많이 함 -> 외부에서 볼 수 있는 것이 줄어듬 -> 변경하기 쉬움(유연성/융통성)

캡슐화 측정법 : class private data를 접근할 수 있는 함수의 개수가 많을수록 캡슐화정도가 낮다.

따라서 맴버함수 vs 비맴버함수 = 비맴버함수(승).
비맴버 프렌드함수 vs 비맴버 비프랜드함수 = 비맴버 비프렌드 함수(승)


또는 namespace 이용 가능.
namespace WebBrowserStuff
{
class WebBrowser { ... };
void clearBrowser(WebBrowser& wb);    //편의함수 : 단순한 함수 집함. 사실 없어도 기능상 문제없음.
}

=> 굉장히 자연스러움.
왜? namespace는 class와 달리 여러 개의 src 파일에 나뉘어 흩어질 수 있다. namespace만 가져오면 바로 쓸 수 있기 때문에.(캡슐화 정도 위험요소)
하지만, 비맴버 비프렌드 함수의 편의함수가 정의되어 있기 때문에(접근성 동일), 캡슐화 위험을 고려하지 않아도 된다. 

namespace의 편의함수를 깔끔히 나누는 방법
-> head파일을 만들어서 관련 기능 함수만 넣는다.

ex)
webbrowser.h
namespace WebBrowserStuff
{
class WebBrowser { ... };
...    //핵심 함수(기본기능)
}

webbrowserbookmarks.h
namespace WebBrowserStuff
{
...      // 즐겨찾기 관련 편의함수
}

webbrowsercookies.h
namespace WebBrowserStuff
{
...      // 쿠키 관련 편의함수
}

장점 : 사용자는 필요한 기능의 헤더파일을 include하여 사용. 다른 것들은 컴파일 고려 X.
-> 확장이 쉽다. 헤더파일 생성후 네임스페이스 사용, 편의함수 구현. 끝
클래스는 그렇지 않음. (이미 정의된 클래스 자체를 사용자가 확장 불가능)
그렇다면 파생클래스 이용 ? 여전히 기본클래스의 private 데이터 접근 불가능. (덜 좋다)

이것만은 잊지 말자.
- 멤버함수보다는 비멤버 비프렌드함수 사용.
  -> 캡슐화, 패키징 유연성, 기능 확장성


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

class Rational
{
public:
Rational(int numerator = 0, int denominator = 1);     // explicit이 아니다.
int numerator() const;
int denominator() const;
private:
...
}

위 클래스의 사칙연산 구현. -> operator 예.
class Rational
{
public:
...
const Rational operator*(const Rational& rhs) const;
};

예제)
Rational oneEighth(1,8);
Rational oneHalf(1,2);

result = oneHalf * 2;     // O
result = 2 * oneHalf;     // X

-> oneHalf.operator*(2)  // O -> int 2를 rational 생성자로 객체 생성 후 사용.  암시적변환
   2.operator*(oneHalf)  // X -> operator*(2, oneHalf); 도 찾는다. (X)
   => 일관성이 X, 혼합 수치 연산(암시적 변환) O

목표 : 일관성 O, 혼합 수치 연산 O

답 : operator*를 비멤버 함수로 생성.

class Rational
{ ... }
const Rational operator* (const Rational& lhs, const Rational& rhs)
{
return Rational(lhs.numerator * rhs.numerator(), lhs.dominator() * rhs.dominator()
}

추가) friend함수로 만들 필요 있는가? 
- 없다. 위 함수는 Rational의 public 인터페이스만 사용해서 구현이 가능하기 때문.

이것만은 잊지 말자.
- 어떤 함수에 들어가는 모든 매개변수(this객체 포함) 에 대해 타입변환이 필요하면 "비멤버함수"


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

swap 함수 보완

swap함수)
namespace std
{
template<typename T>
void swap(T& a, T& b)
{
T temp(a);
a = b;
b = temp;
}
}

3줄의 복사코드. 낭비가 생기는 경우가 있다. 
->

class WidgetImple=
{
public:
...<br />
private:
int a,b,c;
std:vector<double> v;
};

class Widget
{
public:
Widget(const Widget& rhs);
Widget& operator=(const Widget& rhs)
{
*pImpl = *(rhs.pImpl);
}

private:

WidgetImpl* pImpl;          //Widget swap시 Widget, WidgetImpl  각각 3번 복사됨(비효율).이 포인터값만 swap하면                                   된다.
}

=> swap 특정 객체에 특수화
namespace std
{
template<>
void swap<Widget>(Widget& a, Widget& b)
{
swap(a.pImpl, b.pImpl);    // 포인터만 맞바꾼다.
}
}

// 그러나 현재 상태로는 pImpl이 private 객체이므로 Widget 클래스 안에 pulbic swap 함수 만들어 사용 필요. (p.176)

다른 예제)
WidgetImpl, Widget이 template로 구현된 경우. ( p.177)
-> template 부분 특수화의 경우 class에는 가능하지만 함수에는 불가능.
->std swap 오버로딩 함수를 만든다. ( p.178)

->그러나 std 함수 오버로딩 시에, 완전 템플릿 특수화는 가능하지만 새로운 템플릿 추가는 불가능.

->따라서 std가 아닌 새로운 namepsace 에서 swap을 만들면 해결. 



template<typename T>
void doSomething(T& obj1, T& obj2)
{
 using std::swap;
 ...
 swap(obj1, obj2);  //어떤 swap을 쓸까?
}

어떤 swap 함수를 사용할까?
1. std에 있는 일반형 버전
2.std의 일반형을 특수화한 버전(있을 수도 없을 수도 있다.)
3.std가 아닌 T 타입 전용 버전(있을 수도 없을 수도 있다.)

빨간색 부분=>  T 타입 전용 swap이 있으면-> 호출, 없으면 일반 std swap 호출.

하지만 std::swap(~) 사용시 무조건 std의 swap 호출.


swap맴버 함수에서 예외를 발생하시키지 않아야 한다.
-> 클래스(또는 클래스 템플릿)가 강력한 예외 안전성 보장을 제공하도록 도움을 줄 수 있다. : 자세히는 항목 29

이것만은 잊지 말자.
- std::swap이 여러분의 타입에 느리게 동작한다면 swap 멤버 함수 사용.(예외 던지지 않도록)
- 멤버 swap은 비멤버 swap을 호출해야 한다. 클래스(템플릿이 아닌)에 대해서는 std::swap도 특수화 한다.
- swap 호출 시 using 선언을 사용하여 namespace 한정 없이 swap을 호출한다.
- 사용자 정의 타입에 대한 std 템플릿 완전 특수화는 가능, 그러나 std에 새로 추가하려하지 말자(미정의 동작)


반응형

'프로그래밍언어 > C++' 카테고리의 다른 글

[Effective C++] 항목 29~31  (0) 2018.04.10
[Effective C++] 항목 26~28  (0) 2018.04.10
[Effective C++] 항목 18 ~ 21  (0) 2018.01.23
[Effective C++] 항목 13 ~ 17  (0) 2018.01.23
[Effective C++] 항목 5 ~ 12  (0) 2018.01.23

댓글