41
EFFECTIVE C++ 정정 Chapter 6 정정정 정정정정 정정

Effective c++ 정리 chapter 6

  • Upload
    -

  • View
    863

  • Download
    3

Embed Size (px)

Citation preview

EFFECTIVE C++ 정리Chapter 6상속과 객체지향 설계

public 상속은 is - aITEM 32

Item 32: public 상속은 is-a• Derived class is a Base class• class Person { … };• class Student : public Person { … };• Student is Person• 부모 클래스는 보다 일반적인 버전• 자식 클래스는 특수화된 버전• But Person is not a Student.

Item 32: public 상속은 is-a• void eat ( const Person& p );• eat( person );• eat( student );

• void study ( const Student& s );• study( person ); //error : Person is Not a Student.• study( student );

Item 32: public 상속은 is-a• 자연어의 is a 와 다른 경우도 있다 .• class Bird {

public : virtual void fly(); // 새는 날 수 있다 . ㅇㅇ

}• class Penguin : public Bird { … };• Penguin::fly() //??

Item 32: public 상속은 is-a• 해결 방안 1• class Bird { … };• class FlyingBird : public Bird {

virtual void fly(); // 나는 새 클래스를 별도로};• class Penguin : public Bird { … }; // 팽귄은 일반 새 상속

Item 32: public 상속은 is-a• 해결 방안 2

• class Bird { virtual void fly();

• };• class Penguin : public Bird {

virtual void fly() { error(“Attempt to make a penguin fly!”);}// 상속받되 fly 실행시 에러발생

};

Item 32: public 상속은 is-a• 두가지 버전의 차이점• Penguin p;

p.fly(); // 어찌되었건 에러를 낸다• 런타임에 에러 출력하느냐 컴파일 타임에 에러 출력하느냐 .• 컴파일 시점에 알 수 있다면 VS 가 빨간 밑줄 쳐준다 .• 1 버전이 효율 면에서는 단연 뛰어나다 .

Item 32: public 상속은 is-a• 자연어의 is a 와 다른 경우도 있다 . 2

• class Rectangle { … }; // 직사각형• class Square : public Rectangle { … }; // 정사각형 is a 직사각형• makeWider( Rectangle& r ){ // 가로로만 늘리는 함수

int oldHeight = r.height();r.setWidth( r.width() + 10 );assert( r.height == oldHeight ); //height 는 변하지 말았으면…

}

Item 32: public 상속은 is-a• 자연어의 is a 와 다른 경우도 있다 . 2• MakeWider( square );

• Square 의 일관성을 해치거나• Wider 내부의 Assert 에 걸리거나

• 기존의 상식을 통해 단순히 is a 판단하지 말자 .• public 상속의 룰을 고려하여 c++ 적 is-a 관계를 판단할 것

상속에서의 이름 영역ITEM 33

Item 33: 상속에서의 이름 영역• 이름• 부르는 명칭 + 가르키는 대상의 일치• 이름이 유효한 영역이 분리된다 .

• int x = 2; // 전역 변수void ScopeFunc() {

double x = 3.0f; // 로컬 변수std::cin >> x ; // ???

}

Item 33: 상속에서의 이름 영역• 이름 찾기• 가까운 영역부터 검색• 점차 상위 스코프로 검색• 찾았으면 바로 그 대상으로 인식

• ScopeFunc 의 예시에서• x 는 로컬 변수 double x 로 인식

상위 Scope

하위 Scope

Item 33: 상속에서의 이름 영역• 상속에서 이름 영역• 부모 클래스가 상위 scope• 자식 클래스가 하위 scope• 이름 오버라이딩을 하면

• 오버로딩까지 다 덮어씀 .

• virtual 과 무관• 어떤 이름을 찾을 것인가 ? 의 문제

부모 Scope

자식 Scopemf1( )

mf1( int I )

mf2( )

mf3( char c )

mf1( )

mf2( )

mf3( )

Item 33: 상속에서의 이름 영역class Base {public :

virtual void mf1( );void mf1( int i );void mf2( );void mf3( char c );

}

class Derived : public Base {public :

virtual void mf1( );void mf3( );

}

Item 33: 상속에서의 이름 영역Derived dObject;dObject.mf1(); //Derived::mf1()dObject.mf1( 1 ); //error 이름은 찾았는데 인자가 안 맞음dObject.mf2(); //Base::mf2()dObject.mf3(); //Derived::mf3()dObject.mf3( ‘c’ ); //error 이름은 찾았는데 인자가 안 맞음

Item 33: 상속에서의 이름 영역• 부모의 이름 영역까지 가지고 오고 싶다면 ?• using 키워드 사용

• using Base::mf1 // 직접 이름 불러오기• 전달 함수 사용

• virtual void mf1() { Base::mf1(); } // 부모의 이름 영역에서 함수 호출• private 상속관계에서도 사용할 수 있다는 장점 (?)

인터페이스 상속과 구현 상속ITEM 34

Item 34: 인터페이스 상속과 구현 상속• 인터페이스 상속• 멤버 함수의 선언만을 상속받는 것• 순수 가상함수

• 구현 상속• 멤버 함수의 정의까지 상속받는 것• 가상 함수 및 비 가상 함수

Item 34: 인터페이스 상속과 구현 상속class Shape{

public:

virtual void draw() const = 0; virtual void error( const std::string& msg);

int objectID() const ;};

Item 34: 인터페이스 상속과 구현 상속• 추상 클래스 shape 의 강력한 지배력• 멤버함수 인터페이스는 항상 상속된다 .• shape 를 상속 받았다면 반드시 draw, error, objectID 호출 가능하다 .

• 각각의 함수의 특성• draw : 순수 가상 함수 , 객체의 그리는 방법을 정의한다 .• error : 가상 함수 , 에러 출력을 담당한다 .• objectID : 객체의 ID 를 리턴한다 .

Item 34: 인터페이스 상속과 구현 상속• 순수 가상함수 : draw• 객체별로 반드시 구현해야하는 인터페이스

• 각각의 shape 는 반드시 그리는 기능이 있어야한다 .• 하지만 각각의 shape 를 그리는 방법은 서로 다르다 .

• 정의하지 않으면 컴파일 에러• 정의를 제공하는 것도 가능하다 .

• shape::draw(); //shape 에 정의된 draw 를 호출한다 .

Item 34: 인터페이스 상속과 구현 상속• 가상 함수 : error• 인터페이스 뿐 아니라 기본 구현도 상속된다 .• 객체 별로 따로 구현할 수도 있다 .

• 기본 구현체가 있는 것이 위험할 수도 있다 .• 상속받는 새로운 객체가 별도의 지정이 필요한 경우 실수하기 쉽다 .

• 비가상 함수로 default 함수를 만들고 가상함수는 순수 가상으로 만드는 대안 1• 순수 가상함수로 선언하고 , 따로 정의하여 필요한 경우 namespace 를 통한 호출 .

Item 34: 인터페이스 상속과 구현 상속• 비 가상 함수 : objectID• 자식 객체에서 따로 정의해서는 안되는 필수 구현 상속• 객체의 종류에 의존하지 않는 불변동작을 정의할 때 사용된다 .• objectID 를 구하는 방법을 shape 에서 미리 정의해두고 바꾸지 않는 것이

일관적으로 동작할 수 있도록 유도한다 .

Item 34: 인터페이스 상속과 구현 상속• 각각을 적시적소에 사용하는 것이 중요하다 .• 모든 멤버함수를 비가상으로 하는 오류

• 성능이슈는 차후에 처리할 것 ( 암달의 법칙 )• 반드시 필요한 가상 함수는 상속받게 하자 .

• 모든 멤버함수를 가상으로 하는 오류• 첫번째는 불필요한 성능이슈• 특정 인터페이스 구현을 강제할 때 비가상이 필요하다 .

가상함수의 대안들ITEM 35

Item 35: 가상함수의 대안들• Non-Virtual Interface (NVI)• 인터페이스 함수와 구현 함수를 따로 만든다 .• 인터페이스 함수는 비가상 & public

• 인터페이스 함수가 구현함수를 호출한다 . (wrapper function)

• 구현함수는 가상 & private or protected• 구현 함수는 상속 받는 객체가 변경 해서 쓴다 .

Item 35: 가상함수의 대안들• class GameCharacter {

public : int healthValue() const { // 인터페이스 함수return doHealthValue(); // 내부에서 구현함수 호출}

private:virtual int doHealthValue() const { … } // 구현 함수

}

Item 35: 가상함수의 대안들• Non-Virtual Interface (NVI)• 사전 동작 / 사후 동작이 상속받는 모든 클래스에 공통인 경우

• 자원 초기화 , 해제 동작 등• 인터페이스 함수는 언제 동작을 수행할지를 정의• 구현 가상 함수는 어떻게 구현할지를 정의

• 부모의 구현을 그대로 쓰고 싶으면 protected 로 설정

Item 35: 가상함수의 대안들• 함수 포인터로 구현• NVI 와 유사• Stategy 패턴의 단순한 버전

• 구현 함수 대신 함수 포인터를 받아서 사용• 받은 함수 포인터는 멤버변수로• 인터페이스 함수에서 함수포인터를 호출

Item 35: 가상함수의 대안들• class GameCharacter {

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

int healthValue() const { // 인터페이스 함수return healthFunc(*this); // 내부에서 함수 포인터 호출}

private:HealthCalcFunc healthFunc; // 구현 함수 포인터

}

Item 35: 가상함수의 대안들• 함수 포인터로 구현• 융통성 있는 활용

• 같은 클래스 다른 동작• 실행 함수를 동적으로 변경 가능

• 접근 레벨이 낮아진다 .• 클래스 멤버가 아닌 이상 private 접근 불가 .• 필요한 데이터가 있다면 캡슐화 수준을 낮추는 방법밖에 ;;

Item 35: 가상함수의 대안들• std::function (tr1::function) 으로 구현• 개념은 함수 포인터와 동일• 융통성있는 활용

• 모든 callable entitiy 를 사용가능• 약한 함수 포인터 타입 적용 ( 리턴 , 패러미터 암묵적 형 변환 지원 )• 멤버 함수에 this, 및 인자 바인딩하여 적용 가능 (std::bind)

Item 35: 가상함수의 대안들• classic Stategy Pattern• 체력 계산 클래스 계통을 따로 만든다 .

• HealthCalculator, FastCalculator, SlowCalculator

• Character 클래스가 적합한 체력 계산 클래스를 멤버로 들고 있는다 .• 인터페이스 함수에서 계산 클래스의 계산 함수를 호출• 전체적 구조는 NVI 와 유사

상속받은 비가상 함수 재정의ITEM 36

Item 36: 상속받은 비가상 함수 재정의• 비가상 함수는 정적 바인딩으로 묶인다 .• 같은 객체의 정적 포인터를 변경시키는 경우 문제 발생• 상속받은 비가상 함수를 재정의하면

• 이름 탐색 우선순위에 따라서 자식 클래스의 함수를 호출한다 .• 같은 객체의 같은 함수 호출이 서로 달라질 수 있다 ..

• 상속시킨 비가상 함수는 불변의 동작을 구현해 놓은 것 .• 그것을 재정의하는 순간 의미론적으로 모순이다 .

• 상속 계통에서 소멸자를 가상함수로 만드는 것도 같은 의미이다 .

상속된 함수의 기본 매개변수 재정의ITEM 37

Item 37: 상속된 함수 기본 매개변수 재정의• 기본 매개변수 : 함수 선언에서 default 인자를 설정하는 것• 가상함수의 호출은 동적으로 바인딩 된다 .• 하지만 기본 매개변수는 정적 포인터에 따라 결정된다 .• 비가상 함수의 재정의 문제가 똑같이 발생한다 .

• NVI 방식을 통해 해결 가능• 비가상 인터페이스 함수에서 기본 매개변수를 정의해주면 된다 .• 비가상 함수를 재정의 하지 않는 이상 문제 없이 동작한다 .

is-implemented 는 객체 합성으로ITEM 38

Item 38: is-implemented 는 객체 합성으로• 객체 합성은 클래스를 멤버 변수로 갖고 있는 것• has – a 관계 • Person 과 Address 의 관계

• person has a address

• is-implemented-in-term-of ( 객체를 써서 구현되는 ) 관계• is – a 처럼 완전히 같은 멤버들을 상속받지는 않는다 .• 대신 다른 객체의 함수를 사용하여 구현부에 적용

Item 38: is-implemented 는 객체 합성으로• set 의 예제• set 을 구현할 때 linked-list 의 형식을 빌려오고 싶다 .• std::list 를 상속받으면 ? (is – a 관계 )

• 중복 원소를 가질 수 있다는 속성까지 상속받는다 .• 하지만 set 에서는 중복된 원소를 가져서는 안 된다 .

• 따라서 list 를 객체 합성 (has-a) 하여 활용하되 전부를 받아쓰진 않는다 .• 이게 바로 is-implement-in-terms-of