effective c++(chapter 5,6)

82
Effective C++ Chapter 5 NHN NEXT 장문익

Upload: -

Post on 12-Jul-2015

145 views

Category:

Technology


6 download

TRANSCRIPT

Page 1: Effective c++(chapter 5,6)

Effective C++ Chapter 5

NHN NEXT 장문익

Page 2: Effective c++(chapter 5,6)

변수정의는최대한늦춘다.

변수를 정의하면 생성자가 호출되는 비용과 소멸자가 호출되는 비용이 든다.

사용되지 않아도 이 비용은 발생한다.

쓰지도 않을 객체가 만들어 지는 일을 예방할 수 있다.

변수의 의미가 명확한 상황에서 초기화가 이루어지기 때문에, 변수의 쓰임새를 문서화하는 데도 도움된다.

Page 3: Effective c++(chapter 5,6)

변수를정희함과동시에초기화

std::string encryptPassword( const std::string& password )

{

std::string encrypted( password ); // 변수 정의와 동시에 초기화, 복사생성자 사용

encypt( encrypted );

return encrypted;

}

Page 4: Effective c++(chapter 5,6)

루프에서변수선언

// A 방법 : 루프 바깥쪽에 정의

Widget w;

for ( int I = 0; I < n; ++ I ) {

w = i에 따라 달라지는 값

}

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

// B 방법 : 루프 안쪽에 정의

for ( int I = 0; I < n; ++I ) {

Widget w( I에 따라 달라지는 값 );

}

생성자 n번 + 소멸자 n번

Page 5: Effective c++(chapter 5,6)

루프에서의변수선업

대입이 생성자 – 소멸자 쌍보다 비용이 덜 들고

전체 코드에서 수행 성능에 민감한 부분을 건드리는 중이라고 생각되지 않는다면

B방법을 선택

Page 6: Effective c++(chapter 5,6)

C++ 캐스트

const_cast: 객체의 상수성(constness)을 없애는 용도로 사용된다.

dynamic_cast: 주어진 객체가 어떤 클래스 상속 계통에 속한 특정 타입인지 아닌지를 결정하는 작업에 쓰인다. 런타임 비용이 높은 캐스트 연산자.

reinterpret_cast: 포인터를 int로 바꾸는 등의 하부 수준 캐스팅을 위해 만들어진 연산자

static_cast: 암시적 변환(비상수 객체를 상수 객체로, int를 double로 바꾸는 등의 변환)을 강제로진행할 때 사용한다.

구형 스타일의 캐스트를 쓰기보다는 C++ 스타일의 캐스트를 쓰면 발견하기도 쉽고, 설계자가 어떤역학을 의도했는지가 더 자세하게 드러납니다.

Page 7: Effective c++(chapter 5,6)

잘못된 static_cast 사용

class Window {

public:

virtual void onResize() { … }

};

class SpecialWIndow : public Window {

public:

virtual void onResize() {

static_cast<Window>(*this).onResize();

}

};

// 사본인 임시 객체에서 호출된 onResize

Page 8: Effective c++(chapter 5,6)

파생 클래스의 함수를 호출하고자 하고, 그 객체를 조작할 수 있는 수단으로 기본 클래스의 포인터밖에 없을 경우, 어떻게 해야 하는가?

파생 클래스 객체에 대한 포인터를 컨테이너에 담아둠으로써 각 객체를 기본 클래스 인터페이스를통해 조작할 필요가 없도록 한다.

Page 9: Effective c++(chapter 5,6)

바람직하지않은코드예시

class Window { … };

class SpecialWindow: public Window {

public:

void blink();

};

typdef

std::vector<std::tr1::shared_ptr<Window>

> VPW;

VPW winPtrs;

for ( VPW::iterator iter = winPtrs.begin();

iter != winPtrs.end(); ++iter ) {

if (SpecialWindow *psw =

dynamic_cast<SpecialWindow*>(iter->get()))

Page 10: Effective c++(chapter 5,6)

바람직한코드예시

typdef

std::vector<std::tr1::shared_ptr<Window>> VPSW;

VPSW winPtrs;

for ( VPSW::iterator iter = winPtrs.begin();

iter != winPtrs.end(); ++iter ) {

(*iter)->blink();

}

Page 11: Effective c++(chapter 5,6)

파생 클래스의 함수를 호출하고자 하고, 그 객체를 조작할 수 있는 수단으로 기본 클래스의 포인터밖에 없을 경우, 어떻게 해야 하는가?

다른 종류의 포인터를 담기 위한 타입 안전성을 갖춘 컨테이너가 여러 개 필요하다. 앞에 예시한 코드가 완전하지는 않다.

원하는 조작을 가상 함수 집합으로 정리해서 기본 클래스에 넣어두는 방식으로 처리할 수 있다.

Page 12: Effective c++(chapter 5,6)

코드예시

class Window {

public:

virtual void blink() {}

};

class SpecialWindows: public Window {

public:

virtual void blink() { … }

};

Page 13: Effective c++(chapter 5,6)

폭포식 dynamic_cast

class Window { … };

typedef std::Vector<std::tr1::shared_ptr<Window>> VPW;

VPW winPtrs;

for ( VPW::iterator iter = winPtrs.begin(); iter != winPtrs.end(); ++iter)

{

if ( SpecialWIndow1 *psw1 = dynamic_cast<SpecialWindow1*>(iter->get())) { … }

else if (SpecialWIndow1 *psw2 = dynamic_cast<SpecialWindow1*>(iter->get())) { … }

3, 4, 5, …

} // 이런 코드를 보면 가상 함수 호출에 기반을 둔 방법으로 변경해야 한다.

Page 14: Effective c++(chapter 5,6)

항목 28: 내부에서사용하는객체에대한 ‘핸들'을반환하는코드는피하자

클래스 데이터 멤버는 아무리 숨겨봤자 그 멤버의 참조자를 반환하는 함수들의 최대 접근도에 따라캡슐화 정도가 정해진다.

멤버 변수가 private으로 선언되어도 이 멤버 변수의 참조자를 반환하는 함수가 public이면 실질적으로 이 멤버 변수는 publi이다.

어떤 객체에서 호출한 상수 멤버 함수의 참조자 반환 값의 실제 데이터가 그 객체의 바깐에 저장되어 있다면, 이 함수의 호출부에서 그 데이터의 수정이 가능하다.

즉, 외부 공개가 차단된 멤버 함수에 대해, 이들의 포인터를 반환하는 멤버 함수를 만드는 일이 절대로 없어야 한다.

Page 15: Effective c++(chapter 5,6)

반환타입에 const?

class Rectangle {

public:

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

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

};

Page 16: Effective c++(chapter 5,6)

const 반환의문제점

여전히 내부 데이터에 대한 핸들을 반환하고 있다.

dangling handle 문제가 발생할 수 있다.

Page 17: Effective c++(chapter 5,6)

dangling handle

class GUIObject { ... };

const Rectangle boungingBox( const GUIObject& obj );

GUIObject *pgo;

...

const Point *pUpperLeft = &( bouboundingBox( *pgo ).upperLeft() );

Page 18: Effective c++(chapter 5,6)

위코드의문제점

boundingBox() 함수를 호출하면 Rectangle의 임시 객쳬가 만들어 진다.

이 객체를 temp라고 하면, temp에 대해 upperLeft가 호출될 텐데, 이 호출로 인해 temp의 내부데이터에 대한 참조가가 나온다.

이 참조자에 &연산자를 건 결과 값(주소)이 pUpperLeft 포인터에 대입된다.

이 문장이 끝나면 임시 객체 temp는 소멸한다. temp가 소멸하면 내부 데이터 또한 소멸한다.

결과적으로 upperleft 포인터가 가리키는 객체는 사라졌다.(dangling handle)

Page 19: Effective c++(chapter 5,6)

항목 29: 예외안정성확보

class PrettyMenu {

public:

...

// 배경그림바꾸는멤버함수

void changeBackground( std::istream& imgSrc);

...

private:

Mutex mutex; // 뮤텍스

Image *bgImage; // 현재배경그림

int imageChages; // 배경그림바뀐횟수

};

void PrettyMenu::changeBackground

( std::istream& imgSrc ) {

lock( &mutex ); // 뮤텍스획득

delete bgImage; // 이전배경삭제

++imageChages;

bgImage = new Image( imgSrc ); //새배경등록

unlock( &mutex ); // 뮤텍스해제

}

Page 20: Effective c++(chapter 5,6)

위코드의문제점

“new Image(imgSrc)” 문장에서 예외를 던지면 unlock 함수가 실행되지 않는다.

뮤텍스가 획득된 상태로 유지된다.

“new Image(imgSrc)” 문장에서 예외를 던지면 bgImage가 가리키는 객체는 이미 삭제된후이다.

예외가 발생했음에도 불구하고imageChanges 변수는 증가한다.

Page 21: Effective c++(chapter 5,6)

reference-counting smart pointer 사용한자원관리객체

void PrettyMenu::changeBackground( std::istream& imgSrc )

{

Lock ml( &mutex ); // RCSP를자원관리객체

// 뮤텍스획득후, 필요없을때바로해제

delete bgImage; // 이전배경삭제

++imageChages;

bgImage = new Image( imgSrc ); // 새배경등록

}

Page 22: Effective c++(chapter 5,6)

자료구조오염문제(예외안전성을갖춘함수)

예외 안전성을 갖춘 함수는 basic guarantee, strong guarantee, nothrow guarantee 중 하나를제공한다.

Page 23: Effective c++(chapter 5,6)

basic guarantee

함수 동작 중에 예외가 발생하면, 실행 중인 프로그램에 관련된 모든 것들을 유효한 상태로 유지하겠다는 보장

자료구조 오염을 막을 수 있다.

프로그램의 상태가 정확히 어떠한지 예측이 안 될 수 있다.

Page 24: Effective c++(chapter 5,6)

strong guarantee

함수 동작 중에 예외가 발생하면, 프로그램의 상태를 절대로 변경하지 않겠다는 보장

이런 함수를 호출하는 것은 원자적인(atomic) 동작이라 한다.

호출이 성공하면 마무리까지 완벽하게, 호출이 실패하면 함수 호출이 없었던 것처럼 프로그램 상태가 되돌아 간다.

프로그램 상태 예측이 쉽다(성공, 실패)

Page 25: Effective c++(chapter 5,6)

nothrow guarantee

예외를 절대로 던지지 않겠다는 보장

약속한 동작은 언제나 끝까지 완수하는 함수

기본제공 타입에 대한 모든 연산은 nothrow guarantee

Page 26: Effective c++(chapter 5,6)

예외안전성보장

class PrettyMenu {

public:

std::tr1::shared_ptr<Image> bgImage;

};

void PrettyMenu::changeBackground( std::istream& imgSrc )

{

Lock ml( &mutex );

bgImage.reset( new Image( imgSrc ) ); // bgImage의내부포인터를 "new Image" 표현식의

// 실행결과로변경

++imageChages;

}

Page 27: Effective c++(chapter 5,6)

copy-and-swap

어떤 객체를 수정하고 싶으면 그 객체의 사본을 하나 만들어 놓고 그 사본을 수정한다.

수정 동작 중에 실행되는 연산에서 예외가 던져지더라고 원본 객체는 바뀌지 않은 채로 남아 있다.

필요한 동작이 전부 성공적으로 완료되고 나면 수정된 객체를 원본 객체와 맞바꾼다.

이 전략은 대개 ‘진짜‘객체의 모든 데이터를 별도의 구현 객체에 넣어두고,

그 구현 객체를 가리키는 포인터를 진짜 객체가 물고 있게 하는 식으로 구현한다.

pimpl 관용구

Page 28: Effective c++(chapter 5,6)

copy-and-swap 예시(계속)

struct PMImpl {

std::tr1::shared_ptr<Image> bgImage;

int imageChanges;

};

class PrettyMenu {

...

private:

Mutex mutex;

std::tr1::shared_ptr<PMImpl> pImpl;

};

Page 29: Effective c++(chapter 5,6)

copy-and-swap 예시

void PrettyMenu::changeBackground( std::istream& imgSrc )

{

using std::swap;

Lock ml( &mutex );

std::tr1::shared_ptr<PMImpl> pNew( new PMImpl( *pImpl ) ); // 객체의데이터부분을복사

pNew->bgImage.reset( new Image( imgSrc ) ); // 사본을수정한다.

swap( pImpl, pNew );

}

Page 30: Effective c++(chapter 5,6)

함수가제공하는예외안전성보장의강도

void someFunc()

{

... // 이함수의현재상태에대해사본만들어놓는다.

f1();

f2();

... // 변경된상태를바꾸어넣는다.

}

f1은 기본적인 보장만 제공

someFunc 함수에서 강력한 보장을 제공하게하려면?

f1에 의해 프로그램 상태는 변화하므로someFunc의 보장 강도와 상관없이 내부에서호출하는 함수들이 제공하는 보장 강도를 따르게 된다.

Page 31: Effective c++(chapter 5,6)

항목 30: 인라인함수

함수처럼 보이고 함수처럼 동작한다.

매크로보다 훨씬 안전하고 쓰기 좋다.

함수 호출 시 발생하는 오버헤드도 걱정할 필요가 없다.

인라인 함수를 사용하면 컴파일러가 함수 본문에 대해 문맥별로 최적화하기 용이해진다.

Page 32: Effective c++(chapter 5,6)

인라인함수의특성

인라인 함수의 아이디어는 함수 호출문을 그 함수의 본문으로 바꿔치기 하자는 것이다.

목적 코드의 크기가 커진다. 메모리가 제한된 상황에서 남발해서는 안 된다.

가상 메모리를 쓰는 환경일지라도, 페이징 횟수가 늘어나고 명령어 캐시 적중률이 떨어질 가능성도높아진다.

반대로 본문 길이가 굉장히 짧은 인라인 함수를 사용하면 함수 호출문에 대해 만들어지는 코드보다함수 본문에 대해 만들어 지는 코드의 크기가 작아질 수도 있다.

그러면 목적 코드의 크기도 작아지며, 명령어 캐시 적중률도 높아진다.

Page 33: Effective c++(chapter 5,6)

암시적인인라인요청

class Person()

{

public:

// 암시적인인라인요청

int age() const { return theAge; }

private:

int theAge;

};

Page 34: Effective c++(chapter 5,6)

명시적인인라인요청

template<typename T>

inline const T& std::max( const T& a, const T& b )

{ return a < b ? b : a; }

Page 35: Effective c++(chapter 5,6)

인라인요청이거절될수도있다.

선택은 컴파일러가 한다.

루프가 들어있거나 재귀가 들어있으면 거절한다.

가상 함수 호출도 거절한다.

그 밖의 상황때문에도 거절한다.

Page 36: Effective c++(chapter 5,6)

생성자와소멸자는인라인해서는안된다.

class Base {

public:

...

private:

std::string bm1, bm2;

};

class Derived: public Base {

public:

Derived() {}

...

private:

std::string dm1, dm2, dm3;

}

Derived() 생성자를 인라인 요청하면 안된다.

Derived 클래스는 Base 클래스를 상속받기 때문에 Base 클래스의 생성자와 소멸자가 반복해서 인라인된다.

Page 37: Effective c++(chapter 5,6)

항목 31: 파일사이의컴파일의존성줄이기

Page 38: Effective c++(chapter 5,6)

항목 32: public 상속모형은 “is-a”

기본 클래스에 적용되는 모든 것들이 파생 클래스에 그대로 적용되어야 한다.

모든 파생 클래스 객체는 기본 클래스 객체의 일종이기 때문이다.

Page 39: Effective c++(chapter 5,6)

항목 33: 상속된이름을숨기는일은피하자

class Base()

{

private:

int x;

public:

virtual void mf1() = 0;

virtual void mf2();

void mf3();

...

};

class Derived: public Base

{

public:

virtual void mf1();

void mf4();

...

};

Page 40: Effective c++(chapter 5,6)

유효범위

void Derived::mf4()

{

...

mf2();

...

}

이 함수를 호출하면 컴파일러는 mf2() 함수가무엇인지 파악하게 된다.

mf4의 유요범위 내부에서 mf2()를 파악할 수없으므로, 그 바깥 유효범위인 Derived 클래스에서 파악한다.

여전히 mf2()를 파악할 수 없으므로, 그 바깥유효범위인 Base 클래스의 유효범위로 옮겨간다.

Base클래스에서 mf2()를 발견하고 탐색이 종료된다.

Page 41: Effective c++(chapter 5,6)

이름가리기

class Base {

private:

int x;

public:

virtual void mf1() = 0;

virtual void mf1( int );

virtual void mf2();

void mf3();

void mf3(double);

};

class Derived: public Base {

public:

virtual void mf1();

void mf3();

void mf4();

}

Page 42: Effective c++(chapter 5,6)

이름가리기

Derived d;

int x;

d.mf1(); // Derived::mf1 호출

d.mf1(x); // Derived::mf1이 Base::mf1을가린다.

d.mf2(); // Base::mf2 호출

d.mf3(); // Derived::mf3 호출

d.mf3(x); // Derived::mf3이 Base::mf3을가린다.

Page 43: Effective c++(chapter 5,6)

가려진이름을다시보기

class Derived: public Base {

public:

using Base::mf1; // Derived의유효범위

using Base::mf3; // Derived의유효범위

virtual void mf1();

void mf3();

void mf4();

};

Page 44: Effective c++(chapter 5,6)

항목 34: 인터페이스상속과구현상속의차이

함수 인터페이스의 상속

함수 구현의 상속

Page 45: Effective c++(chapter 5,6)

순수가상함수

virtual void draw() const = 0;

어떤순수가상함수를물려받은클래스가해당순수가상함수를다시선언해야한다.

전형적으로추상클래스안에서정의를가지지않는다.

순수가상함수를선언하는목적은파생클래스에게함수의인터페이스만물려주려는것이다.

Page 46: Effective c++(chapter 5,6)

단순가상함수

virtual void error( const std::string& msg)

파생클래스로 하여금 인터페이스를 상속하게 한다는 점은 순수 가상 함수와 유사하다.

파생 클래스 쪽에서 오버라이드할 수 있는 함수 구현부도 제공한다는 점이 다르다.

파생 클래스로 하여금 함수의 인터페이스뿐만 아니라 그 함수의 기본 구현도 물려받게 하자는 것

Page 47: Effective c++(chapter 5,6)

비가상함수

클래스와 상관없이 변하지 않는 동작을 지정하는 데 쓰인다.

파생 클래스가 함수 인터페이스와 더불어 그 함수의 필수적인 구현을 물려받게 하는 것이다.

Page 48: Effective c++(chapter 5,6)

구현과상속

인터페이스만 상속시키려면 순수 가상 함수

인터페이스와 기본 구현을 함께 상속시키려면 단순 가상 함수

인터페이스와 필수 구현을 상속시키려면 비가상 함수

Page 49: Effective c++(chapter 5,6)

클래스설계시주의할점

모든 멤버 함수를 비가상 함수로 선언하지 않는다. 비가상 소멸자는 문제가 될 수 있다.

모든 멤버 함수를 가상 함수로 선언하지 않는다.

Page 50: Effective c++(chapter 5,6)

항목 35: 가상함수대신쓸것들

public 비가상 멤버 함수를 통해 private 가상 함수를 간접적으로 호출하게 만드는 방법

이를 비가상 함수 인터페이스(non-virtual interface: NVI) 관용구라고 한다.

Page 51: Effective c++(chapter 5,6)

비가상함수인터페이스

class GameCharacter {

public:

int healthValue() const

{

...

int retVal = healthValue();

return retVal;

...

}

private:

virtual int doHealthValue() const

{

...

}

};

Page 52: Effective c++(chapter 5,6)

가상함수를함수포인터데이터멤버로

class GameCharacter;

int defaultHealthCalc { const GameCharacter& gc };

class GameCharacter {

public:

typedef int ( *HealthCalcFunc ) ( const GameCharacter& );

explicit GameCharacter( HealthCalcFunc hcf = defaultHealthCalc ) : healthFunc( hcf ) {}

int healthValue() const { return healthFunc (*this); }

...

private:

HealthCalcFunc healthFunc;

};

Page 53: Effective c++(chapter 5,6)

가상함수를 tr1::function1 데이터멤버로

class GameCharacter;

int defaultHealthCalc { const GameCharacter& gc };

class GameCharacter {

public:

typedef std::tr1::function<int ( const GameCharacter& )> HealthCalaFunc;

explicit GameCharacter( HealthCalcFunc hcf = defaultHealthCalc ) : healthFunc( hcf ) {}

int healthValue() const { return healthFunc (*this); }

...

private:

HealthCalcFunc healthFunc;

};

Page 54: Effective c++(chapter 5,6)

한쪽클래스계통에속해있는가상함수를다른쪽계통에속해있는가상함수로

class GameCharacter;

class HealthCalcFunc {

public:

...

virtaul int calc ( const GameCharacter& gc ) const { ... }

...

};

HealthCalcFunc defaultHealthCalc;

class GameCharacter {

public:

explicit GameCharacter( HealthCalcFunc* phcf = &defaultHealthCalc ) : pHealthCalc( phcf ) {}

int healthValue() const { return pHealthCalc->calc( *this ); }

...

private:

HealthCalcFunc *pHealthCalc;

};

Page 55: Effective c++(chapter 5,6)

항목 36: 상속받은비가상함수를파생클래스에서재정의하지말라

상속받은 비가상 함수를 파생 클래스에서 재정의 하게 되면 is-a 관계에 위배된다.

Page 56: Effective c++(chapter 5,6)

두얼굴의동작과비가상함수의정적바인딩

class B {

public:

void mf();

...

};

class D: public B { ... };

D x;

B *pB = &x;

pB->mf();

D *pD = &x;

pD->mf();

x에 대한 포인터를 통해 mf()를 호출한다. 함수도 같고, 객체도 똑같으니, 동작이 같아야 하지않나?

비가상 함수는 정적 바인딩을 묶이기 때문에,

pB는 ‘B에 대한 포인터’ 타입으로 선언되었기때문에, pB를 통해 호출되는 비가상 함수는 항상 B클래스에 정의되어 있을 것이라 결정한다.

Page 57: Effective c++(chapter 5,6)

가상함수는동적바인딩으로묶인다.

위의 코드에서 mf() 함수를 가상 함수로 만들면 동적 바인딩된다.

그럼 pB, pD에서 호출되든 D::mf가 호출된다.

Page 58: Effective c++(chapter 5,6)

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

정적 바인딩: 선행 바인딩

동적 바인딩: 지연 바인딩

객체의 정적 타입: 프로그램 소스 안의 선언문을 통해 그 객체가 갖는 타입

객체의 동적 타입: 현재 그 객체가 진짜로 무엇이냐에 따라 결정되는 타입, ‘이 객체가 어떻게 동작할것이냐'를 가리키는 타입

Page 59: Effective c++(chapter 5,6)

정적타입

class Shape {

public:

enum SHapeColor { Red, Green, Blue };

virtual void draw( SHapeColor color = Red ) const = 0;

...

};

class Rectangle: public Shape {

public:

enum SHapeColor { Red, Green, Blue };

virtual void draw( SHapeColor color = Green ) const;

...

};

class Circle: public Shape {

public:

enum SHapeColor { Red, Green, Blue };

virtual void draw( SHapeColor color ) const;

...

};

Shape *ps; // 정적타입 = Shape*

Shape *ps = new Circle; // 정적타입 = Shape*

Shape *ps = new Rectangle ; // 정적타입 = Shape*

Page 60: Effective c++(chapter 5,6)

동적타입

pc의 동적 타입은 Circle*

pr의 동적 타입은 Rectangle*

ps는 아직 아무 객체도 참조하고 있지 않으므로 동적 타입이 없다.

ps = pc; // ps의동적타입은이제 Circle*

ps = pr; // ps의동적타입은이제 Rectangle*

Page 61: Effective c++(chapter 5,6)

가상함수는동적바인딩된다.

pc->draw( Shape::Red ); // Circle::draw( Shape::Red ) 호출

pr->draw( Shape::Red ); // Rectangle::draw( Shape::Red ) 호출

Page 62: Effective c++(chapter 5,6)

기본매개변수값이설정된가상함수는동적?

정적?

가상함수는 동적으로 바인딩되어 있다.

기본 매개변수는 정적으로 바인딩되어 있다.

그래서 파생 클래스에 정의된 가상 함수를 호출하면서 기본 클래스에 정의된 기본 매개변수 값을 사용해 버릴 수 있다.

Page 63: Effective c++(chapter 5,6)

비가상인터페이스관용구를이용한개선

class Shape {

public:

enum ShapeColor { Red, Green, Blue };

void draw( ShapeColor color = Red ) const

{

doDraw( color );

}

...

private:

virtual void doDraw( ShapeColor color ) const = 0;

};

class Rectangle: public Shape {

public:

...

private:

virtual void doDraw( ShapeColor color ) const;

...

};

Page 64: Effective c++(chapter 5,6)

항목 38: has-a 혹은 is-implemented-in-

terms-of 를모형화할때는객체합성을사용

합성(composition): 어떤 타입의 객체들이 그와 다른 타입의 객체들을 포함하고 있을 경우에 성립하는 그 타입들 사이의 관계를 일컫는다.

레이어링(layering), 포함(containment), 통합(aggregation), 내장(embadding)과 같은 용어도사용된다.

Page 65: Effective c++(chapter 5,6)

합성예시

class Address { ... };

class PhoneNumber { ... };

class Person {

public:

...

private:

std::string name;

Address address;

PhoneNumber voiceNumber;

PhoneNumber faxNumber;

};

Page 66: Effective c++(chapter 5,6)

응용영역과구현영역

응용 영역(application domain): 객체 중 일상생활에서 볼 수 있는 사물을 본 뜻 것들

구현 영역(implementation domain): 버퍼, 뮤텍스, 탐색 트리 등 순수하게 시스템 구현만을 위한인공물

객체 합성이 응용 영역의 객체들 사이에서 일어나면 has-a 관계

구현 영역에서 일어나면 그 객체 합성의 의미는 is-implemented-in-terms-of 관계

Page 67: Effective c++(chapter 5,6)

항목 39: private 상속은심사숙고해서

class Person { ... };

class Student: private Person { ... };

void eat( const Person& p );

void study( const Student& s );

Person p;

Student s;

eat( p );

eat( s ); // error! s는 Person의일종이아니다. private 상속은 is-a 관계가아니다.

Page 68: Effective c++(chapter 5,6)

private 상속

public 상속과 대조적으로, 클래스 사이의 상속 관계가 private이면 컴파일러는 일반적으로는 파생클래스 객체를 기본 클래스 객체로 변환하지 않는다.

기본 클래스로부터 물려받은 멤버는 파생 클래스에서 모조리 private 멤버가 된다.

private 상속은 is-implemented-terms-of와 유사하다.

Page 69: Effective c++(chapter 5,6)

class Timer {

public:

explicit Timer( int tickFrequency );

virtual void onTick() const;

...

};

class Widget: private Timer {

private:

virtual void onTick() const;

...

};

private 상속으로 is-a 관계가 아니다.

하지만 Timer 클래스의 onTick()은 public이므로 호출할 수도 있다는 오해를 일으킬 수 있다.

Page 70: Effective c++(chapter 5,6)

private 상속을 public 상속 + 객체합성조합으로

class Widget{

private:

class WidgetTimer: public Timer {

public:

virtual void onTick() const;

...

};

WidgetTimer timer;

...

};

Page 71: Effective c++(chapter 5,6)

public 상속 + 객체합성조합의장점

클래스 파생은 가능하게 하되, 파생 클래스에서 onTick을 재정의 할 수 없도록 설계 차원에서 막고싶을 때 유용하다.

만약 Widget을 Timer로부터 상속시킨 구조라면(public이든 private이든 상관없이) 막을 수 있다.

Page 72: Effective c++(chapter 5,6)

public 상속 + 객체합성조합의장점

컴파일 의존성을 최소화하고 싶을 때 좋다.

Widget이 Timer에서 파생된 상태라면, Widget이 컴파일될 때 Timer의 정의도 끌어올 수 있어야 하기 때문에 Widget의 정의부 파일에 Timer.h 같은 헤더를 #include 해야한다.

Page 73: Effective c++(chapter 5,6)

공백기본클래스최적화

공백 클래스(empty class): 비정적 데이터 멤버가 없는 클래스

가상 함수도 하나도 없는 클래스(가상 함수가 한 개라도 있으면 각 객체마다 vptr이 하나씩 추가 되기 때문이다.)

Page 74: Effective c++(chapter 5,6)

공백클래스의메모리크기

class Empty {}; // 정의된데이터없으므로메모리를사용하지말아야한다.

class HoldsAnInt { // int를저장할공간만필요해야한다.

private:

int x;

Empty e; // 메모리요구가없어야한다.

};

sizeof(HoldsAnInt) > sizeof(int); // ?

Page 75: Effective c++(chapter 5,6)

공백클래스의메모리크기

C++은 크기가 0인 독립 구조의 객체 생성을 금지한다.

결국 Empty 클래스의 메모리 크기는 0이 아니라 char 타입을 끼워넣는 1 byte가 된다.

Page 76: Effective c++(chapter 5,6)

공백기본클래스최적화(EBO)

class HoldsAnInt: private Empty {

private:

int x;

};

위처럼 private으로상속받으면

Sizeof(HoldsAnInt) == sizeof(int)가성립한다.

EBO를 적용하면 파생 클래스의 크기를 증가시키는 일이 거의 없어진다.

Page 77: Effective c++(chapter 5,6)

항목 40: 다중상속은심사숙고해서

다중 상속의 모호성

최적 일치 함수를 찾은 후에 함수의 접근 가능성을 점검한다.

두 checkout()은 일치도가 같으므로 접근 가능성을 점검할 절차를 거칠 수 없다.

class BorrowableItem {

public:

void checkOut();

...

};

class ElectronicGadget {

private:

bool checkOut() const;

};

class MP3Player:

public BorrowableItem,

public ElectronicGadget

{ ... };

MP3Player mp;

mp.checkOut(); // 어떤 checkOut()?

Page 78: Effective c++(chapter 5,6)

죽음의 MI 마름모꼴(deadly MI diamond)

class File { ... };

class InputFile: public File { ... };

class OutputFile: public File { ... };

class IOFile : public InputFile, public OutputFile { .. };

Page 79: Effective c++(chapter 5,6)

가상상속의문제점

가상 상속을 사용하는 클래스로 만들어진 객체는 가상 상속을 쓰지 않은 것보다 일반적으로 크기가더 크다.

가상 기본 클래스의 데이터 멤버에 접근하는 속도도 비가상 기본 클래스의 데이터 멤버에 접근하는속도보다 느리다.

가상 상속이 되어 있는 클래스 계통에서는 파생 클래스들로 인해 가상 기본 클래스 부분을 초기화해야 한다.

그래서 가상 기본 클래스는 피할 수 있으면 피하고, 피할 수 없다면 가상 기본 클래스에는 데이터를넣지 않도록 한다.

Page 80: Effective c++(chapter 5,6)

다중상속을잘쓰려면?

인터페이스 클래스로부터 public 상속을 시키고,

동시에 구현을 돕는 클래스로부터 private 상속을 시킨다.

Page 81: Effective c++(chapter 5,6)

인터페이스클래스를만들고,

// 인터페이스

class IPerson {

public:

virtual ~IPerson();

virtual std::string name() const = 0;

virtual std::string birthDate() const = 0;

};

Page 82: Effective c++(chapter 5,6)

구현을돕는클래스를만들고,

// 인터페이스를구현하는데사용할함수들

class PersonInfo {

public:

explicit PersonInfo( DatabaseID pid );

virtual ~PersonInfo();

virtual const char* thename() const;

virtual const char* thebirthDate() const;

virtual const char* valueDelimOpen() const;

virtual const char* valueDelimClose() const;

...

};