이펙티브 c++ 5,6 장 스터디

18
effective c++ 구현, 상속, 객체지향 설계

Upload: quxn6

Post on 12-Jul-2015

216 views

Category:

Software


12 download

TRANSCRIPT

Page 1: 이펙티브 C++ 5,6 장 스터디

effective c++ 구현, 상속, 객체지향 설계

Page 2: 이펙티브 C++ 5,6 장 스터디

구현

Page 3: 이펙티브 C++ 5,6 장 스터디

특정 함수 내에서 변수가 선언된 후 예외 때문에 반환되어 버린다면 자원 낭비다.

또, 생성 시 초기화(복사 생성자)를 하지 않으면

생성과 대입이 두 번에 걸쳐 일어나게 되므로 이 또한 낭비다.

So, 될 수 있으면 자원은 사용하기 직전에 초기화 하자.

루프에 사용할 변수를 만드는 경우,

루프 밖에서 정의하고, 안에서 대입하는 경우(A)와

루프 안에서 복사 생성자로 정의하는 경우(B),

두 가지로 나누어 볼 수 있다.

A : 생성자 1번 + 소멸자 1번 + 대입 n번

B : 생성자 n번 + 소멸자 n번

대입이 생성자+소멸자 보다 저렴할 때, A방법을 쓰자. (반대라면 B)

But, A방법은 유효범위가 넓어지므로 프로그램 이해도 및 유지보수에 안 좋을 수 있다.

변수 정의는 늦출 수 있는 데까지 늦추자

Page 4: 이펙티브 C++ 5,6 장 스터디

구형 캐스트 : int(varname) , (int)varname

C++ 신형 캐스트

const_cast<> : 변수에 붙어있는 const를 뗄 때 사용

dynamic_cast<>

: 안전한 다운캐스팅을 위해 해당 객체가 특정 상속계통에 속한 것인지 아닌지를 판단

이상한 게 if문 안에 대입연산자로 넣어주면서 판단을 함.

Window window;

if ( SpecialWindow *psw = dynamic_cast<SpecialWindow*>(window))

** 성능이 안 좋다. 특히 폭포식은 위험함

reinterpret_cast<> : 하부 수준 캐스팅(bit수준), 구현환경에 의존적, 잘안씀

static_cast<> : 이외 다른 캐스트들(흔히 씀)

** doSomeWork(Widget(15)); 같은 방식은 객체를 만드는 게 아니라 int형을 캐스팅하는거

구형 캐스트 보다는 C++ 스타일 캐스트를 쓰자.

But, 될 수 있으면 캐스팅이 없는 것이 좋은 설계다!!

캐스팅은 절약, 또 절약

Page 5: 이펙티브 C++ 5,6 장 스터디

핸들 : 다른 객체에 손댈 수 있게 하는 매개자(참조자, 포인터, 반복자 등도 큰 의미에서 핸들)

Point& upperLeft() const { return pData->ulhc; }

라는 함수가 있다할 때, 얼핏 보면 const라 ulhc를 바꿀 수 없을 것 같아 보인다.

하지만 이건 pData->ulhc 그 자체를 다른 객체로 대체할 수 없을 뿐(대입 연산이 안됨)

ulhc객체 내부의 set함수 등을 이용하여 객체를 바꾸는 것은 가능하다. 따라서

const Point& upperLeft() const { return pData->ulhc; }

식으로 머리에 const를 붙이면 반환 값이 상수가 되서 객체 자체를 상수화 한다.

문제는 해결되는 듯 하지만, 핸들은 결국 캡슐화를 해치고 dangling pointer를 만들 가능성이

있다.

내부에서 사용하는 객체에 대한 ‘핸들’을 반환하는 코드는 피하

Page 6: 이펙티브 C++ 5,6 장 스터디

예외 안전성을 가진 함수는..

자원이 새지 않는다.(중간에 예외발생으로 종료되어도 메모리릭 없이)

자료구조가 더럽혀지지 않는다.(변수 및 포인터들이 가리키는 값들이 정상적이어야..)

세 가지 중 하나의 방식으로 보장한다.

기본적인 보장 : 위의 두 가지를 만족, But 결과를 사용자가 예측할 수 없음

강력한 보장 : 원자적으로 동작, 호출이 성공하면 완벽한 성공, 실패하면 호출이 없었던 것처

럼!!

예외불가 보장 : 예외를 절대로 던지지 않겠다. 기본제공 타입 등이 보장되나 대부분은 어렵다.

결국 강력한 보장이 되도록 만드는 게 좋다. 어떻게?

스마트 포인터를 적극 활용하자.

reset()등의 함수는 확실히 new가 될 때만 가지고 있는 자원을 delete한다.

mutex등도 자원관리 담당 객체를 활용하자.

Copy-and-swap 방식을 활용하자!

pimpl 관용구를 활용하자(25번 항목의 swap도 참고)

하위 함수들의 예외 안전성이 강력하지 못하면, 본 함수도 보장할 수 없다.

예외 안전성을 확보하자

Page 7: 이펙티브 C++ 5,6 장 스터디

Inline함수는 호출문을 함수 본체로 바꿔치기 한다.

함수호출 시 오버헤드가 없다. 컴파일러가 문맥별 최적화를 해줄 수 있다.

But 메모리가 부족한 환경에서 쓰면 성능이 떨어진다.

컴파일러 선에서 무시할 수 있다.

So, 작은 함수에 사용하자.(함수 호출 오버헤드만큼 작은..)

암시적인 방법 : 선언부에서 정의까지, int age() const { return value; }

명시적인 방법 : 정의 앞에 inline 키워드 붙이기.

가상함수 호출은 안됨 : inline은 컴파일러가 하는 데, 가상함수 호출은 runtime에 하니까

생성자, 소멸자에는 쓰지 말 것 : 컴파일러가 변수나 부모 클래스의 생성자도 실행하는 부분을

슬쩍 끼워넣을텐데 그 모든 것이 inline으로 만들어져야 함

Inline함수는 이해하고 사용하자

Page 8: 이펙티브 C++ 5,6 장 스터디

정의부에 대한 의존성을 선언부에 대한 의존성으로

포인터, 참조자에 대해서는 클래스 전방 선언으로 의존도를 줄일 수 있다. (실제 객체의 경우

컴파일 할 때 컴파일러가 그 크기를 알아야 하는데 껍데기만 있는 전방선언 클래스로는 알 수

없음.)

핸들 클래스 이용(pimpl관용구 사용)

person이라는 선언부를 위한 클래스와 personImpl이라는 정의부를 위한 클래스 두 개로 쪼갬

선언부 클래스와 구현부 클래스는 함수가 동일,

선언부 멤버 변수로 구현부 클래스를 포인터로 가짐, 구현부는 실제 값들을 가짐.

선언부 함수들의 구현은 구현부 함수들을 호출하는 걸로. 생성자에서는 pimpl포인터 초기화

사용자는 person만 include하면 됨, 이때 person이 바로 핸들 클래스

인터페이스 클래스 (팩토리 함수 이용)

person이라는 인터페이스 클래스와 이를 상속받은 realPerson 두 클래스로 구현

person에서는 필요한 함수들을 순수 가상 함수로 선언, 팩토리 함수인 create을 구현

static create()함수는 person 포인터형에 realPerson을 담아서 제공.

** 둘 다 추가적으로 비용이 발생하지만 의존성이 낮아지니 일단 쓰자 for 사용자

파일 사이의 컴파일 의존성을 최대로 줄이자!!

Page 9: 이펙티브 C++ 5,6 장 스터디

상속, 그리고 객체 지향 설계

Page 10: 이펙티브 C++ 5,6 장 스터디

public 상속은 is-a 관계다.

즉, 자식 클래스는 부모가 될 수 있어도

부모 클래스는 자식이 될 수 없다.

** private은 다르다!!

public 상속 모형은 반드시 is-a를 따르도록 하자

Page 11: 이펙티브 C++ 5,6 장 스터디

부모와 자식 클래스에 동일한 이름의 함수가 있을 때,

자식 클래스의 객체는 해당 함수를 호출하기 위해

먼저 가까운 유효범위(자식 클래스)를 뒤지고 부모 클래스로 간다.

즉, 부모 클래스의 함수는 가려진다.

문제는 부모 클래스에 있는 동일 이름의 오버로딩된 다른 함수도 가려진다는 것.

이 때, using Base::mf1; 등으로 가려진 함수를 꺼내 쓸 수 있다.

전달 함수로 꺼낼 수도 있음

상속된 이름을 숨기는 일은 피하자

Page 12: 이펙티브 C++ 5,6 장 스터디

순수 가상 함수를 선언하는 것은 인터페이스만 물려주려는 것

가상 함수를 선언하는 것은 인터페이스와 그 함수의 기본 구현 형태를 물려주려는 것.

: 상속받은 함수만의 구현 형태가 없다면 기본 구현 형태를 써도 좋다.

** 순수 가상함수의 정의부분을 만들어 두고 자식 클래스에서 사용하는 방법도 있다.

비가상 함수를 선언하는 것은 인터페이스와 구현을 둘 다 물려주려는 것.

: 이 함수는 재정의 하지 마라. 이 형태로 써라!!

상속 시 주의할 점

상속관계가 생긴다면 기본 클래스에는 최소한 가상 함수가 하나는 있을 것이다.(소멸자라도..)

모든 함수를 가상 함수로 만드는 것도 좋지 않다.(자신감이 없나? 하는 느낌을 줄 수도!!)

인터페이스 상속과 구현 상속의 차이를 제대로 구별하자

Page 13: 이펙티브 C++ 5,6 장 스터디

NVI (비가상 함수 인터페이스)

public : healthValue()는 private : virtual doHealthValue()를 호출한다.

여기서 healthValue가 인터페이스로 사용되며 wrapper라 부른다.

wrapper를 통해서 doHealthValue()가 언제 실행될 지를 정하고

각 파생클래스에서는 doHealthValue()의 정의를 구현한다.

** 여기서 doHealthValue는 protected일 수도 있다.(파생클래스에서 기본 구현함수를 쓸 때)

함수 포인터로 구현한 전략

외부에 함수를 만들고 클래스 내부에서 함수 포인터를 이용 클래스 객체 자신을 매개변수로 하

여 기능을 수행하는 방법. 함수 포인터에 대한 개념공부가 더 필요할 듯

tr1::function 도 함수포인터를 제공해준다.

전략 패턴 사용

특정 기능 수행만 전담으로 하는 클래스를 외부에 만들고 원래 클래스가 그 클래스에 대한 객

체를 가리키는 포인터를 들고 있게 하는 방법, 포인터는 생성자에서 초기화.

가상 함수대신 쓸 것도 생각해보자

Page 14: 이펙티브 C++ 5,6 장 스터디

비가상 함수를 파생클래스에서 재정의하면 부모 클래스형 포인터에서 자식 클래스 객체를 가

리킬 경우 부모 클래스의 비가상함수가 호출된다. virtual이 아니니깐

상속받은 비가상 함수는 절대 재정의 하지 말자

상속받은 비가상 함수를 파생클래스에서 재정의하는 것은 금물

Page 15: 이펙티브 C++ 5,6 장 스터디

상속받은 함수를 재정의 한다는 건 일단 가상 함수만 가능하다.

문제는 기본 매개변수 값은 정적으로 바인딩되고 가상 함수는 동적으로 바인딩 된다는 것.

따라서 부모 클래스 형 포인터로 자식 클래스 객체를 가리킬 때,

부모 클래스의 매개변수를 이용해서 자식 클래스의 함수를 호출하게 된다.

상속받은 함수의 기본 매개변수 값은 절대로 재정의 하지 말자

Page 16: 이펙티브 C++ 5,6 장 스터디

어떤 클래스가 다른 클래스의 객체를 멤버 변수로 가지고 있을 때, 이를 객체 합성이라 한다.

객체 합성은 두 가지 측면에서 접근할 수 있는데,

현실에 있는 사물을 본 뜬 응용영역에서는 has-a 측면으로 접근하고

시스템 구현을 위한 구현영역에서는 is-implemented-interm-of 측면에서 접근하자.

std::list로 set을 구현할 때, 이를 상속받는 것이 아니라 객체 합성하여 내부적으로 접근해야..

has-a 혹은 is-implemented-interm-of 를 모형화할 때는 객체 합성을 사용하자

Page 17: 이펙티브 C++ 5,6 장 스터디

private 상속은 is-implemented-in-terms-of 를 의미한다.

객체 합성과 달리 private상속은 공백 기본 클래스 최적화를 활성화 할 수 있다.

private상속은 심사숙고해서 구현하자

Page 18: 이펙티브 C++ 5,6 장 스터디

다중 상속은 모호성 문제를 일으킬 수 있다. 따라서 가상 상속을 활용하면 좋다.

연산 복잡도를 줄이기 위해 가상 상속 기본 클래스에는 데이터를 두지 말자.

하나는 인터페이스를 하나는 구현을 돕는 private 상속을 이용하는 것이 적법하다.

다중 상속은 심사숙고해서 구현하자