반응형
항목 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:
private:Rational(int numerator = 0, int denominator = 1); // explicit이 아니다.int numerator() const;int denominator() const;
}...
위 클래스의 사칙연산 구현. -> 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 |
댓글