effective c++chapter4

46
Effective C++ Chapter 4 정정

Upload: -

Post on 13-Aug-2015

37 views

Category:

Technology


2 download

TRANSCRIPT

Page 1: Effective c++chapter4

Effective C++ Chapter 4 정리

Page 2: Effective c++chapter4

Item 18

인터페이스 설계는 제대로 쓰기는 쉽게 , 엉터리로 쓰기는 어렵게 하자

Page 3: Effective c++chapter4

Item 18 : 인터페이스 설계를 잘하자

인터페이스 ?함수 , 템플릿 , 클래스 등등 코드와 사용자가 만나는 지점

- 어떤 인터페이스를 쓰고 컴파일 되었다면 사용자가 생각 한대로 움직여야 한다

인터페이스 , 제대로 쓰기는 쉽게 , 엉터리로 쓰기는 어렵게 하는 법을 알아보자

Page 4: Effective c++chapter4

Item 18 : 인터페이스 설계를 잘하자

1. 타입을 이용하자

미사일의 데미지를 설정하고 싶다고 하자 .

만약 데미지가 항상 1,3,5,7 의 값만 가져야 한다면 어떨까 ?

void setDamage(int damage); // 항상 1,3,5,7 만 쓰시오 . 이렇게 주석으로 ?

Enum MissileDamage{ …} // 여기서 1,3,5,7 값만 정의

void setDamage(MissileDamage damage); // 제한된 값만 이용가능

Page 5: Effective c++chapter4

Item 18 : 인터페이스 설계를 잘하자

1. 타입을 이용하자

미사일의 데미지를 설정하고 싶다고 하자 .

만약 데미지가 항상 1,3,5,7 의 값만 가져야 한다면 어떨까 ?

void setDamage(int damage); // 항상 1,3,5,7 만 쓰시오 . 이렇게 주석으로 ?

Enum MissileDamage{ …} // 여기서 1,3,5,7 값만 정의

void setDamage(MissileDamage damage); // 제한된 값만 이용가능

const 도 적극 이용하자 .

Page 6: Effective c++chapter4

Item 18 : 인터페이스 설계를 잘하자

2. 그렇게 하지 않을 이유가 없다면 기본 제공 타입처럼 만들자

사용자에게 가장 익숙한 타입은 기본 제공 타입이다 . 일관성은 인터페이스

설계의 중요한 덕목이다 . 사용자가 뭔가 외워서 사용해야 하는 인터페이스는

잘못 쓰기가 쉽다 .

Page 7: Effective c++chapter4

Item 18 : 인터페이스 설계를 잘하자

3. 자원관리를 위해서 스마트 포인터를 사용하자

Missile* createMissile();

Missile 을 내부적으로 할당해 그 포인터를 반환하는 팩토리 함수이다 .

Missile 의 사용이 끝나면 해당 객체를 할당 해제 해줘야 한다 .

하지만 사용자는 해제 안 해줄수도 , 두 번 해줄수도 있다 . 여러 문제를 피하기 위해

스마트포인터를 적극적으로 사용하자 .

std::unique_ptr<Missile> createMissile(); // 받는 쪽에서 unique 로 할지 shared 로 할지 정할 수 있다 . 해제도 자동으로

한다 .

Page 8: Effective c++chapter4

Item 19

클래스 설계는 타입 설계와 똑같이 취급하자

Page 9: Effective c++chapter4

Item 19 : 클래스 설계 == 타입 설계

새로운 클래스를 정의한다 == 새로운 타입을 정의한다

좋은 클래스 설계란 ?

- 문법 (syntax) 이 자연스럽고 , 의미구조 (semantics) 가 직관적이며 , 효율적인 구현이 한 가지 이상 가능해야 한다 .

Page 10: Effective c++chapter4

Item 19 : 클래스 설계 == 타입 설계

좋은 클래스 설계를 위한 질문1. 새로 정의한 타입의 객체의 생성 및 소멸은 어떻게 이루어져야 하는가 ?클래스의 생성자 및 소멸자의 설계와 연관 , operator new, delete 등과 연관

2. 객체 초기화는 객체 대입과 어떻게 달라야 하는가 ?

3. 새로운 타입으로 만든 객체가 값에 의해 전달되는 경우에 어떤 의미를 줄 것인가 ?

4. 새로운 타입이 가질 수 있는 적법한 값에 대한 제약으로 무엇을 잡을 것인가 ?항상 유지되어야 하는 (invariant) 값들의 관리 , 멤버 변수 “쓰기” 작업에 대한 고려

5. 기존의 클래스 상속 계통망 (inheritance graph) 에 맞출 것인가 ?

이어서…

Page 11: Effective c++chapter4

Item 19 : 클래스 설계 == 타입 설계

6. 어떤 종류의 타입 변환을 허용할 것인가 ?암시적 , 명시적 , 함수를 통해서 , 무엇으로 변할 수 있게 할 것인가

7. 어떤 연산자와 함수를 두어야 의미가 있을까 ?

8. 표준 함수들 중 어떤 것을 허용하지 말 것인가 ?

9. 새로운 타입의 멤버에 대한 접근 권한을 어느 쪽에 줄 것인가 ?public, protected, priavte 의 문제

10. ‘ 선언되지 않은 인터페이스’로 무엇을 둘 것인가 ?클래스가 보장할 수 있는 수행성능 , 예외 안전 , 자원사용등의 문제

이어서…

Page 12: Effective c++chapter4

Item 19 : 클래스 설계 == 타입 설계

11. 새로 만든 타입이 얼마나 일반적인가 ?템플릿을 이용해서 만들어야 하는가 ?

12. 정말로 꼭 필요한 타입인가 ? 비멤버함수나 , 템플릿을 통해 만들 수 없는가 ?

Page 13: Effective c++chapter4

Item 20

` 값에 의한 전달 ` 보다는` 상수객체 참조자에 의한 전달 `방식을 택하는 편이 대개 낫다

Page 14: Effective c++chapter4

Item 20 : 웬만하면 상수객체 참조자에 의한 전달 방식을 쓰자

` 값에 의한 전달 `?

함수로부터 객체를 전달 받거나 전달 할 때매개변수가 전달 인자의 ` 사본 ` 에 의해 초기화 되는 것

누누이 이야기 들어온 예 , 만약 엄청 큰 객체의 사본 만들기 x 2000000

비효율의 비효율의 비효율이다 .

그 객체가 상속 -> 상속 -> 상속 이었다면

각 사본이 만들어질 때마다 상속받은 모든 클래스에 대한 객체들이

파바박 하고 만들어진다 .

Page 15: Effective c++chapter4

Item 20 : 웬만하면 상수객체 참조자에 의한 전달 방식을 쓰자

` 상수객체 참조자 ` const T&

bool validateMissile(const Missile& m); // 유효한 미사일인지 확인하는 함수

정말로 복사되어야 하는 상황이 아니라면 !

해당 타입의 멤버 변수들의 확인해서 뭔가 작업하는 것이라면 !

const 로 값을 바꾸지 않게 제약을 두고 ` 사본 ` 을 만들지 않도록 참조를 사용하는

것이다 .

Page 16: Effective c++chapter4

Item 20 : 웬만하면 상수객체 참조자에 의한 전달 방식을 쓰자

` 상수객체 참조자 ` const T&

또 ` 복사 손실의 문제 ` 도 막을 수 있다 .

복사손실의 문제는 파생 클래스 객체가 기본 클래스 객체로서 전달되는 경우에

기본 클래스의 복사 생성자가 호출되면서 파생클래스의 멤버변수가 떨어지게 되는

상황을 말한다 . 상수객체는 그 자신을 넘기므로 이런 문제에서 해방된다 .

Page 17: Effective c++chapter4

Item 20 : 웬만하면 상수객체 참조자에 의한 전달 방식을 쓰자

` 값에 의한 전달 ` 을 사용할 수도 있는 경우

STL 반복자나 함수 객체의 경우 예전부터 값으로 전달 되도록 설계 되었다 .

다만 반복자와 함수 객체를 구현 할 때 반드시

1. 복사효율을 높일 것2. 복사손실 문제에 노출되지 않도록 할 것

위 두 항목이 요구된다 .

또 기본 타입의 경우 내부적으로 포인터를 사용하는 참조에 비해 값에 의한 전달로 더 적은 메모리를 사용할 수 도 있다 .

Page 18: Effective c++chapter4

Item 20 : 웬만하면 상수객체 참조자에 의한 전달 방식을 쓰자

기본타입이 값에 의한 전달을 사용할 수 있으니 하게 되는 착각

1. 내가 만든 타입도 작기만 하면 괜찮지 않나 ?만약 내가 포인터 하나만 들고 있는 경우 어짜피 사용하는 메모리 크기도 같은데 값에 의한 전달을 하면 안되나 하고 생각할 수 있지만 오산이다 . 왜냐하면 복사 할때 그 포인터가 들고 있는 객체를 복사해줘야 할테니까

2. 또 어떤 컴파일러는 사용자 정의 타입과 기본 타입을 다르게 취급하기도 한다 .

이 때문에 메모리 크기만 덜 사용한다고 사용자 정의 타입을 ` 값에 의한 전달 ` 로 마구 넘겨서는 안된다 .

마지막으로 사용자 정의 타입은 언제나 그 크기가 변할 수 있으므로 항상 ` 값에 의한 전달 ` 을 주의 해야 한다 .

Page 19: Effective c++chapter4

Item 21

함수에서 객체를 반환해야 할 경우에 참조자를

반환하려고 들지 말자

Page 20: Effective c++chapter4

Item 21 : 함수에서 객체를 참조자로 반환하지 말자

앞서 20 에서 다룬 것은 ` 전달 ` 의 문제고 이번은 ` 반환 ` 의 문제이다 .

` 값에 의한 전달 ` 을 지양 했으니 모든 부분에 ` 값에 의한 전달 ` 을 지우고 싶다 !!

라고 생각하면 큰일이다 .

값에 의한 전달이 아닌 방식으로 어떤 객체를 반환하려고 할 때

취할 수 있는 방법 두 개를 소개 하고 문제점을 살펴 보겠다 .

Page 21: Effective c++chapter4

Item 21 : 함수에서 객체를 참조자로 반환하지 말자

1. 지역객체 참조자로 반환하기

const Missile& createMissile(){

Missile m(); return m;}

안돼 .. 으윽 .. 이러면 안돼 !

지역 객체로 생성된 m 은 그저 그 block 안에서만 살아 있을 뿐이야 !!

m 을 반환 받은 애는 m 을 써보지도 못할꺼야 ..ㅜ .ㅜ

Page 22: Effective c++chapter4

Item 21 : 함수에서 객체를 참조자로 반환하지 말자

2. new 로 생성하고 참조자로 반환하기

const Missile& create();{

Missile* m = new Missile(); return *m;}

으… 안돼

저 new 로 할당된 Missile 은 누가 언제 어떻게 delete 해주지 ??

ㅜ .ㅜ

Page 23: Effective c++chapter4

Item 21 : 함수에서 객체를 참조자로 반환하지 말자

그러니까 객체를 복사해서 넘겨줘야 할 때는 복사하자

const Missile create();{ return Missile();}

참조자를 반환할 것인가 , 객체를 반환할 것인가를 결정할 때의 원칙

올바른 동작이 이루어지는가 ?

비용의 문제는 컴파일러에게 맡겨두자

Page 24: Effective c++chapter4

Item 22

데이터 멤버가 선언될 곳은private 영역이다 .

Page 25: Effective c++chapter4

Item 22 : 데이터 멤버는 private 영역에 선언하자

왜 안 public, protected 요 ?

1. 문법적 일관성의 문제

- 어떤 클래스는 멤버변수에 접근할 때 그냥 접근하고 어떤 클래스는 함수로 접근하게 하지 말자 . 다 똑같은 방법 ( 함수 ) 로 접근하게 하자

2. 읽기 쓰기 접근 권한을 클래스 작성자가 제어할 수 있다 .

- 함수를 만들어 주는 것으로 제어 가능

3. 캡슐화

어떤 데이터 멤버를 얻는데 다른 작업을 거쳐야 한다면 해당 작업을 마친 후 전달할 수 있다 .

Page 26: Effective c++chapter4

Item 22 : 데이터 멤버는 private 영역에 선언하자

C++ 의 public 은 캡슐화 되지 않았다는 것을 의미한다 . 실질적으로 캡슐화 되지 않았다는 것은 ` 바꿀 수 없음 ` 을 의미` 바꾸면 안됨 ` 을 의미한다고 생각하는게 더 쉬울지도… 널리 쓰이는 클래스일수록 해당 캡슐화 해서 사용해야 한다 .

일례로 어떤 클래스의 public 데이터를 제거한다고 할 때를 생각해 보자 .

이 멤버에 매달려 있는 수 많은 코드들은 망가지고 만다 . 캡슐화 되지 않은 멤버변수

가 이렇게 위험하다 .

protected 는 뭐가 다른가 ?

그렇지 않다 . 바로 위의 예에서 public 을 protected 로 바꿔보라 . 같은 결과가 나온다 .

Page 27: Effective c++chapter4

Item 23

멤버 함수보다는 비멤버 비프렌드 함수와

더 가까워지자

Page 28: Effective c++chapter4

Item 23 : 멤버 함수보다는 비멤버 비프렌드 함수

캡슐화 ?

어떤 것을 캡슐화하면 , 외부에서 이것을 볼 수 없게 된다 . 캡슐화 = 캡슐화 대상을 바꾸는 유연성 증가 , 캡슐화 볼 수 있는 대상 감소

어떤 멤버 변수에 접근하는 함수의 개수 증가 = 개략적인 캡슐화 정도 감소

public 상태면 이 캡슐화 정도가 그냥 0, 아무나 접근할 수 있으니까

private 멤버 변수에 접근할 수 있는 함수는 해당 클래스에 있거나

프렌드 클래스에 있다 .

따라서 비멤버 비프렌드 함수의 사용은 캡슐화 정도를 증가 시킨다 .

Page 29: Effective c++chapter4

Item 23 : 멤버 함수보다는 비멤버 비프렌드 함수

비멤버 비프랜드 함수 예시

// 클래스와 비멤버 비 프랜드 함수를 같은 namespace 공간에 놓음namespace WebBrowserStuff {

class WebBrowser{…}; // 이 클래스에서 clearBrowser 를 선언할 수도 있었다

void clearBrowser(WebBrowser& wb); // 비멤버 비프랜드 함수를 만들었다 . }

namespace 는 여러 개의 소스 파일에 나뉘어 흩어질 수 있다 .

따라서 다른 소스파일에서 WebBrowser 에 관련된 내용을 만들고 같은

namespace 로 묶을 수 있게 된다 .

Page 30: Effective c++chapter4

Item 23 : 멤버 함수보다는 비멤버 비프렌드 함수

표준 함수에 대한 내용인 std:: 이 namespace 를 사용해 구성되어 있다 .

이런 식의 구성은 새로운 비멤버 비프렌드 함수의 추가도 쉽게 할 수 있게 해준다 .

Page 31: Effective c++chapter4

Item 24

타입 변환이 모든 매개변수에적용되어야 한다면

비멤버 함수를 선언하자

Page 32: Effective c++chapter4

Item 24 : 타입변환이 모든 매개 변수에 적용 돼야 할 때는 비멤버 비프렌드 함수를 쓰자

유리수 클래스를 가지고 이번 항목을 설명 할 것이다

class Rational {public:

// explicit 를 붙이지 않았다// int 에서 Rational 로의 변환을 허용하기 위해Rational(int numerator = 0, int denominator = 1);

…// 곱셈 연산을 지원하는 operatorconst Rational operator* (const Rational& rhs) const;

};

Page 33: Effective c++chapter4

Item 24 : 타입변환이 모든 매개 변수에 적용 돼야 할 때는 비멤버 비프렌드 함수를 쓰자Rational oneHalf = Rational(1,2);

Rational result = oneHalf * 2; // Ok! (1 번 )result = 2* oneHalf; // error (2 번 )

왜 1 번은 ok 이가 2 번은 error 인가 ?

1 번의 바뀐 표현 = 3 번 result = oneHalf.operator*(2);2 번의 바뀐 표현 = 4 번 result = 2.operator*(oneHalf);

1 번의 경우 매개변수 리스트에 있는 2 에 암묵적 변환이 일어난다

2 번의 경우 매개변수 리스트에 들어있지 않기 때문에 , Rational 에 들어있는 멤버 함수를 사용하기 때문에 2 를 Rational 로 취급하지 않는다 . 암묵적 변환이 일어나지 않는다 .

Page 34: Effective c++chapter4

Item 24 : 타입변환이 모든 매개 변수에 적용 돼야 할 때는 비멤버 비프렌드 함수를 쓰자

Rational result = oneHalf * 2; // Ok! (1 번 )result = 2* oneHalf; // error (2 번 )

우리의 목표는 2 번도 잘 되게 하는 것이다 .

방법 * 연산을 할 때 lhs 도 rhs 도 암묵적 변환이 일어나게 하자 . 1. 멤버 함수가 아닌 비멤버 비프렌드 함수를 사용하자멤버 함수를 호출하는 객체는 암묵적 변환이 되지 않는다고 했다 . 따라서 비멤버비프렌드 함수를 사용하는 것이다 .

2. 매개 변수 리스트에 lhs 와 rhs 를 넣자이어서 …

Page 35: Effective c++chapter4

Item 24 : 타입변환이 모든 매개 변수에 적용 돼야 할 때는 비멤버 비프렌드 함수를 쓰자

위의 내용을 적용한 새로운 operator * 을 만들어 보자

비멤버 비프랜드 함수여야 하므로

class Rational {};

// Rational class 밖에서 선언 및 정의const Rational operator* (const Rational& lhs, const Rational& rhs);

// 매개변수 리스트에 lhs, rhs 모두 넣음

Page 36: Effective c++chapter4

Item 24 : 타입변환이 모든 매개 변수에 적용 돼야 할 때는 비멤버 비프렌드 함수를 쓰자

Rational oneHalf = Rational(1,2);

Rational result = oneHalf * 2; // Ok! (1 번 )result = 2* oneHalf; // error (2 번 )

왜 1 번은 ok 이가 2 번은 error 인가 ?

1 번 result = oneHalf.operator*(2);2 번 result = 2.operator*(oneHalf);

여기서 알 수 있는 것은 암시적 타입 변환이 매개변수 리스트에 들어 있는 객체에만

적용된다는 것입니다 . 따라서 1 번은 ok 이고 2 번은 암시적 변환이 되지 않아 2번인것입니다 .

Page 37: Effective c++chapter4

Item 25

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

Page 38: Effective c++chapter4

Item 25 : 예외를 던지지 않는 swap 에 대한 지원

namespace std{

template<typename T>void swap(T& a, T& b){

T temp(a);a = b;b = temp;

}}

이것이 std::swap(); 함수이다

에누리 없는 복사가 3회 일어난다 .

Page 39: Effective c++chapter4

Item 25 : 예외를 던지지 않는 swap 에 대한 지원

class WidgetImpl{public: …private:

int a, b, c;std::vector<double> v;

}

// wiget 에서 갖고 있을 내용

class Widget {public:

Widget(const Widget& rhs);

Widget& operator=(const Widget&

rhs){

…*pImpl = *(rhs.pImpl);…

}private:

WidgetImpl* pImpl;}

앞으로 내용을 위한 pImpl 관련 내용

Page 40: Effective c++chapter4

Item 25 : 예외를 던지지 않는 swap 에 대한 지원

이전 슬라이드에서 본 예에서

Wiget 객체의 swap 은 pImpl 을 교환 하는 것이면 충분하다

그래서 Widget 에 대한 swap 함수를 다음과 같이 표현 하였다 .

namespace std{// 아직 실행 안됨 , 왜 안 되는지는 다음 슬라이드에서 // 템플릿 특수화 , 어떤 타입에 대한 함수의 실행 방식을 미리 결정해 놓는 것template<> void swap<Widget>(Widget& a, Widget& b){

swap(a.pImpl, b.pImpl); }

}

Page 41: Effective c++chapter4

Item 25 : 예외를 던지지 않는 swap 에 대한 지원

만약 Widget 과 WidgetImpl 이 템플릿 클래스 였다면 ?

template<typename T> class WidgetImpl { … };template<typename T> class Widget { … };

아래처럼 만들고 싶다 .

// c++ 기준에 적합하지 않은 함수 , 이유는 다음 슬라이드namespace std{

template<typename T> void swap<Widget<T>>(Widget<T>& a, Widget<T>& b){

a.swap(b);}

}

Page 42: Effective c++chapter4

Item 25 : 예외를 던지지 않는 swap 에 대한 지원

적합 하지 않은 이유 함수 템플릿에서는 부분 특수화를 허용하지 않음 ( 클래스 템플릿은 허용 )

void swap<Widget<T>> 부분특수화하는 부분

좀 바꿔서 해보려고 해도 안된다

// namespace std 에는 템플릿 추가 불가 , 따라서 아래 코드도 사용 불가namespace std{

template<typename T> void swap(Widget<T>& a, Widget<T>& b) // <Widget<T>> 부분

삭제{

a.swap(b);}

}

Page 43: Effective c++chapter4

Item 25 : 예외를 던지지 않는 swap 에 대한 지원

해결책 : namespace 가 std 가 아닌 곳에 해당 함수를 넣자

// stdWidgetStuffnamespace WidgetStuff{

template<typename T> void swap(Widget<T>& a, Widget<T>& b) // <Widget<T>> 부분

삭제{

a.swap(b);}

}

Page 44: Effective c++chapter4

Item 25 : 예외를 던지지 않는 swap 에 대한 지원

그래서 ! 이 swap 함수를 사용할 때는 어떻게 하느냐 ?

swap 이라는 이름을 가진 함수의 종류1. std 일반형 버전2. std 일반형을 특수화한 버전3. T 타입 전용버전

원하는 것 어떤 T 타입 전용버전이 있으면 T 타입 전용버전이 불리고 , T 타입 전용 버전이 없으면 std 일반형을 부르고 싶다 .

Page 45: Effective c++chapter4

Item 25 : 예외를 던지지 않는 swap 에 대한 지원

template<typenameT> void doSomething(T& objcet1, T object2) {

using std::swap; // std::swap 을 이 함수 안으로 가져옴

swap(object1, object2); // T 타입 전용의 swap 함수를 호출}

}

이게 되는 이유 => 다음 슬라이드

Page 46: Effective c++chapter4

Item 25 : 예외를 던지지 않는 swap 에 대한 지원

컴파일러가 이전 슬라이드의 swap 호출문을 만나면

1. 전역 유효범위 혹은 타입 T 와 동일한 네임스페이스 안에 T 전용의 swap 함수가 있는지 찾음

2. T 전용 함수가 없다면 using std::swap 선언에 의해 std::swap 함수 사용

효율적인 swap 함수의 사용을 위해

이전 내용을 잘 공부해 두자