Study/Effective C++2010.03.08 10:27

합성(composition)이란, 어떤 타입의 객체들이 그와 다른 타입의 객체들을 포함하고 있을 경우에 성립하는 그 타입들 사이의 관계를 일컫습니다. 포함된 객체들을 모아서 이들을 포함한 다른 객체를 합성한다는 뜻인데, 다음과 같은 경우입니다.

class Address { …. };                                                // 누군가의 거주지

class PhoneNumber { …. };

 

class Person

{

public:

   …….

private:

   std::string mane;                                                    // 이 클래스를 이루는 객체 중 하나

   Address address;                                                  // 마찬가지

   PhoneNumber voiceNumber;                                    // 역시 마찬가지

   PhoneNumber faxNumber;                                       // 이것도 마찬가지

Person 객체는 string, Address와 PhoneNumber 객체로 이루어져있습니다. 이런 경우를 두고 합성이라는 말을 사용하는데 다른 말로는 레이어링(layering), 포함(containment), 통합(addregetion), 내장(embedding)이라고도 합니다.

 

항목 32에서 public 상속의 의미가 “is – a(..는 …의 일종이다)”라고 배웠습니다. 객체 합성 역시 의미를 갖고 있습니다. 실제 뜻이 2개나 되는데 2개인 이유는 소프트웨어 개발에서 대응하는 영역이 두가지이기 때문입니다. 사물을 본뜬 사람, 이동수단, 비디오 프레임 등인데, 이런 객체는 소프트웨어의 응용 영역(application domain)에 속하고, 나머지들은 버퍼, 뮤텍스, 탐색 트리 등 순수하게 시스템 구현만을 위한 인공물입니다. 이런 종류의 객체가 구현 영역(implementation domain)이라고 합니다. 여기서 객체 합성이 응용 영역의 객체들 사이에서 일어나면 has –a 관계입니다. 반면, 구현 영역에서 일어나면 is-implemented – in – terms – of 관계를 나타낸것입니다.

 

위의 person 클래스는 has –a 관계입니다. 사람이 이름의 일종, 사람이 주소의 일종이라고는 말할 수 없는것입니다. 그러나 사람이 이름을 가지며 사람이 주소를 가진다고 하는 것은 자연스럽습니다. 이렇기 때문에 is - a관계와 has - a관계의 역할을 헷갈리는 경우는 없습니다.

 

헷갈리는 부분은 is - a관계와 is – implemented – in – terms – of 관계의 차이점 일것입니다.

한 예로, 객체로 구성된 작은 집합, 중복 원소가 없는 집합체를 나타내고 저장 공간도 적게 차치하는 클래스 템플릿이 필요하다고 했을 때, 표준 라이브러리의 set템플릿을 활용해 보기로 합니다.

set템플릿은 원소 한 개당 포인터 세 개의 오버헤드가 걸리도록 구현되어 있습니다.

 

그러므로 표준 라이브러리의 set템플릿은 적당하지 않습니다. 이번엔 list를 사용해보겠습니다.

이번 포인트는 set템플릿을 만들되 list에서 파생된 형태부터 시작되도록 만든다는 것입니다.

set<T>는 list<T>로부터 상속을 받습니다.

template<typename T>                      // set을 만든답시고 list를 잘못 쓰는 방법

class set : public std::list<T> { … };

항목 32의 설명에 따르면 D와 B 사이에 is - a관계가 성립하면 B에서 참인 것들이 전부 D에서도 참이어야 합니다. 하지만 list 객체는 중복 원소를 가질수 있는 컨테이너 입니다. 구현하고 싶은건 중복된 원소가 있어서는 안되는 겁니다.

이들 두 클래스 사이의 관계가 is – a가 될 리 없으므로, public 상속은 지금의 관계를 모형화하는 데 맞지 않습니다.

 

해결법은 set객체는 list 객체를 써서 구현되는 (is implemented in terms of) 형태의 설계가 가능하다는 것입니다.

template<class T>              // set를 만드는 데 list를 제대로 쓰는 방법

class set {

public:

    bool member(const T& item) const;

    void insert(const T& item);

    void remove(const T& item);

    std::size_t size() const;

private:

    std::;ist<T> rep;                         // set 데이터의 내부 표현부

};

 

template<typename T>

bool set<T>::member(const T& item) const

{

             return set<T>::find(rep.begin(), rep.end(), item) != rep.end();

}

 

template<typename T>

void set<T>::insert(const T& item)

{

             If ( !member(item) )   rep.push_back(item);

}

 

template<typename T>

void set<T>::remove (const T& item)

{

             typename std::list<T>::iterator it = std::find(rep.begin(), rep.end(), item);

 

             If ( it != rep.end() )    rep.erase(it);

}

template<typename T>

std::size_t set<T>::size() const

{

             Return rep.size();

}

인라인 함수로 만들어도 될 정도의 구현입니다.

 

이 관계는 is – a가 아니라, is – implemented – in – terms – of 입니다.

 

이것만은 잊지 말자

-         객체 합성(composition)의 의미는 public 상속이 가진 의미와 완전히 다릅니다.

-         응용 영역에서 객체 합성의 의미는 has – a(…는 …를 가짐)입니다. 구현 영역에서는 is – implemented – in – terms – of(…는 ….를 써서 구현됨)의 의미를 갖습니다.

Posted by 최우림 -=HaeJuK=-
Study/Effective C++2010.03.08 10:26

C++에서 상속 받을수 있는 함수 : 가상, 비가상 함수

상속되는 함수는 동적으로 바인딩 된도, 기본 매개변수는 정적 바인딩된다.

가상함수는 동적으로 바인딩되지만, 런타임 효율성 때문에 기본 매개변수 값은 정적으로 바인딩된다.

동적 바인딩은 늦은 바인딩(late binding)이라고도 불리는데, 객체가 실행시 그 순간의 상태에 따라 일어난다. 프로그래밍에서 변수들은 정수나 문자열 등 형태에 따라 그 변수에 저장되는 값의 저장방법이나 조작방법이 달라진다. 전통적인 컴파일러들이나 어셈블러들은 컴파일 시점에 변수의 형식을 할당하는 정적 바인딩을 제공하였으나, 객체지향 언어들은 실행시 키보드 또는 기타 다른 소스로부터 값이 변수에 들어오는 그 순간에 변수의 형식이 결정되는 동적 바인딩을 지원한다. 이른 바인딩(early binding) 또는 정적 바인딩(static binding)과 대비되는 말이다.

//기하학 도형을 나타내는 클래스

Class Shape {

Public :

             Enum ShapeColor{ Red, Green, Blue };

//모든 도형은 자기 자신을 그리는 함수를 제공해야함

Virtual void draw(ShapeColor color = Red) const = 0;

};

 

Class Rectangle : public Shape {

Public :

             //기본 매개변수 값이 달라진 부분을 놓치지 마세요

             Virtual void draw(ShapeColor color=Green) const;

};

 

Class Circle : public Shape{

Public :

             Virtual void draw (ShapeColor color) const;

};

클래스 구조

                           Shape

 


Rectangle                          Circle

 

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

Shape *pc= new Circle;

Shape *pr= new Rectangle;

Ps, pc, pr, 은 모두 Shape 에 대한 포인터로 선언되어 각각의 정적 타입도 모두 Shape* 입니다

객체의 동적타입은 현재 그 객체가 진짜로 무엇이냐에 따라 결정되는 타입 입니다.

‘이 객체가 어떻게 동작할 것이냐’를 가리키는 타입이 동적 타입이라고 하겠습니다.

ps의 동적타입은 없고, Pc의 동적 타입은 Circle* , pr의 동적타입은 Rectangle *  입니다

 

동적타입은 프로그램 실행중에 바뀔수 있습니다.

Ps = pc;              // ps의 동적 타입은 Circle * 으로 바뀜

Ps= pr;                 // ps의 동적 타입은 Rectangle *으로 바뀜

가상함수는 동적으로 바인딩 됩니다(가상함수의) 호출이 일어난 객체의 동적 타입에 따라서 어떤 함수가 호출될지 결종된다는 뜻.

기본매개변수는 정적으로 바인딩 되기 때문에 매개변수 값이 설정된 가상함수는 뭔가 꼬이기 시작합니다. 파생 클래스에 정의된 가상 함수를 호출하면서 기본 클래스에 정의된 기본 매개변수 값을 사용해 버릴수도 있습니다.

 

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

 

Pr의 동적타입은 Rectangle* 이므로 가상함수는 Rectangle 의 것이다

Rectangle::draw 함수의 초기값은 Green 이지만 pr의 정적타입이 Shape*이기 때문에 값을 Shape클래스에서 가져옴. 기본 매개변수 값들 중 하나가 파생클래스에서 재정의 되면 문제가 생김.

 

문제점이 발생되는 이유는 런타임 효율이라는 요소 때문입니다. 함수의 기본매개변수가 동적으로 바인딩된다면 프로그램 실행 중에 가상 함수의 기본 매개변수 값을 결정할 방법을 컴파일러 쪽에서 마련해 주어야할것입니다. 이방법은 컴파일 과정에서 결정하는 현재의 매커니즘보다 느리고 복잡할것이다.

이러한 이유로 지금 효율 좋은 실행 동작을 누릴수 있게 된것입니다

 

해결책
비가상 인터페이스(Non-Virtual Interface) 관용구(NVI 관용구)를 쓰는것. 이 방법은 파생 클래스에서 재정의할 수 있는 가상 함수를 private 멤버로 두고, 이 가상 함수를 호출하는 public 비가상 함수를 기본 클래스에 만들어 두는 것이다
비가상 함수가 기본 매개변수를 지정하도록 할수 있고, 이 비가상 함수의 내부에서 진짜 일을 맡은 가상함수를 호출하게됨.

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 {

Private:

             Virtual void doDraw(ShapeColor) const;          //기본 매개변수 값이 없음

};

이것만은 잊지 말자!

상복받은 기본 매개변수 값은 절대로 재정의해서는 안됩니다. 왜냐하면 기본 매개변수 값은 정적으로 바인딩되는 반면, 가상함수는 동적으로 바인딩되기 때문입니다

Posted by 최우림 -=HaeJuK=-
Study/Effective C++2010.03.08 10:26

처음 시작은 비가상 함수와 가상함수의 가리는 대상적용에 대해서 시작해서

결론으로 "상속받은 가상함수는 재정의하면 안된다" 끝을 냅니다.

 

#include <iostream>

 

class A
{
    public:
     void f()
        {
           std::cout<<"A
"<<std::endl;
        }
};

 

class B: public A
{
     public:
      void f()            //
이녀석이 A f 가려줄 겁니다.
          {
            std::cout<<"B
"<<std::endl;
          }
};

 

 

int main()
{
   B x;         //
자식 타입의 객체


   A *pA = &x;  //
자식타입의 객체에 대한 포인터를 가리킵니다.


   pA->f();      //
객체의 f함수를 호출합니다.

 

   B *pB = &x;  //자식타입의 객체에 대한 포인터를 가리킵니다.


   pB->f();      //
객체의 f함수를 호출합니다.

 

   return 0;
}

 결과는 분명 "A   B" 이렇게 나온다는 전부 알고 계십니다.

 

pA->f(); "A"라는 이유는 함수f() 비가상 함수이기 때문입니다.

 

타입이 A클래스에 대한 포인터 타입으로 선언되었기 때문에

 

이를 통해 호출되는 가상함수는 항상 A클래스에 정의되어 있다고 결정해 버리는 겁니다.

 

가상함수일 경우는 "B B" 라는 결과를 뱉어 겁니다.

 

가상함수는 가상함수 처럼 정적 바인딩이 아닌 동적 바인딩을 사용하기 때문입니다.

 

가상 함수일 경우에 함수호출의 결정요인은 해당 객체가 아니라 객체를 가리키는 포인터의 타입 이라는 겁니다.

public 상속은 is-a 관계 입니다, 그리고 비가상 함수는 클래스 파생에 관계없는 불변동작을 정해주는 입니다.. 불변의 동작을 원하여 만든 함수를 파생 클레스에서 다시 정의 하면 이것이 목적이 있어 다시 정의 하는 것이라면 결국 가상함수를 이유가 전혀 없다는 것입니다.

결국. 목적이 있어 다시 정의하는 함수 경우에는 가상함수를 쓰는 좋습니다.

결국에는 가상함수는 파생 클레스에서 정의 하지 말자라는 이야기 입니다/

Posted by 최우림 -=HaeJuK=-
Study/Effective C++2010.03.08 10:25

1)    비가상 인터페이스 관용구(non-virtual interface)를 사용하자.

-         공개되지 않은 가상 함수를 비가상 public 멤버 함수로 감싸서 호출하는, 템플릿 메서드 패턴의 한 형태

-         NVI 에서 가상 함수가 엄격하게 public 일 필요는 없다.

-         장점 : 가상 함수가 호출되기 전에 어떤 상태를 구성하고 가상 함수가 호출된 후에 그 상태를 없애는 작업을 랩퍼를 통해 공간적으로 보장된다.

                                                     

l       비가상 인터페이스 관용구(non-virtual interface)란?

-         사용자로 하여금 public 비가상 멤버 함수를 통해 private 가상 함수를 간접적으로 호출하게 만드는 방법.

-         관용구에 쓰이는 비가상 함수는 가상함수의 랩퍼라고 부른다.

 

2)    가상함수를 함수 포인터 데이터 멤버로 대체하자.

-         군더더기 없이 전략 패턴의 핵심만을 보여주는 형태.

-         장점 : 다양한 상황에 대처 할 수 있는 유연한 클래스를 만들 수 있다.

프로그램 실행 도중 함수를 바꿀 수 있다.

-         단점 : private 로 선언 된 변수의 경우 함수가 클래스 내부가 아닌 외부에 있기 때문에 변수에 접근할 수 없는 상황이 벌어진다.

 

3)     가상함수를 tr1::funtion 데이터 멤버로 대체하여, 호환되는 시그너처를 가진 함수 호출성 개체를 사용할 수 있도록 만들자

-         전략 패턴의 한 형태.

-         장점 : 함수 포인터 보다 유연한 클래스를 만들 수 있다.(멋진코드)

 

4)    한쪽 클래스 계통에 속해 있는 가상 함수를 다른 쪽 계통에 속해 있는 가상함수로 대체하자.

-         전략 패턴의 전통적인 형태

-         장점 : 표준적인 전략 패턴 구현 방법에 친숙할 경우 빨리 이해 할 수 있다.

 

 

 

 

 

Class GameCharacter

{

Public:

Virtual int healthValue() const;

………

};

 

 

 

EX1) NVI 관용구를 통한 템플릿 메서드 패턴

class GameCharacter

{

public:

   int healthValue() const;

   {

      ...          //이곳에서 어떤 동작을 할 수 있다.

     

        int retVal = doHealthValue();

      ...         //이곳에서 어떤 동작을 할 수 있다.

 

      return retVal;

    }

  ...

private:

 

   virtual int doHealthValue() const

   {

      ...    //케릭터의 체력 계산 구현

   }

};

// healthValue(비가상함수)를 doHealthValue(가상함수)의 랩퍼(wrapper)라고 부름

 

 

 

Ex2) 함수 포인터로 구현

class GameCharacter;   //전방선언

int defaultHealthCalc(const GameCharacter& gc); //이곳에서 계산합니다.

 

class GameCharacter

{

public:

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

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

   //생성자.

 

   int healthValue() const

   {

       return healthFunc(*this);

   }

 

   ....

 

private:

   HealthCalcFunc healthFunc;

};

 

 

 

 

Ex3) tr1::function 구현

class Game Character;   //전방선언

int defaultHealthCalc(const GameCharacter& gc); //이곳에서 계산합니다.

 

class GameCharacter

{

public:

 typedef std::trl::function<int (const GameCharacter& ) > HealthCalcFunc;

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

   //생성자.

 

   int healthValue() const

   {

       return healthFunc(*this);

   }

 

   ....

 

private:

   HealthCalcFunc healthFunc;

};

 

 

Ex4) 고전적인 전략(Strategy) 패턴

class GameCharacter;

class HealthCalcFunc

{

public:

...

virtual 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;

};

 

 

 

 

 

 

 

 

결론

l       가상함수 대신에 쓸 수 있는 다른 방법으로 NVI 관용구 및 전략 패턴을 들 수 있다.

이 중 NVI 관용구는 그 자체가 템플릿 메서드 패턴의 한 예이다.

l       객체에 필요한 기능을 멤버 함수로부터 클래스 외부의 비멤버 함수로 옮기면, 그 비멤버 함수는 그 클래스의 public 멤버가 아닌 것들을 접근할 수 없다는 단점이 생긴다.

l       tr1::function 객체는 일반화된 함수 포인터처럼 동작 한다. 이 객체는 주어진 대상 시그너처와 호환되는 모든 함수 호출성 개체를 지원한다.

 

 

 

시그너처란 ?

: 함수의 매개변수와 반환 타입

extern int x; //객체 선언

std::size_t numDigits(int number, int number2, int a); // 함수 선언

class Widget; // 클래스 선언

template<typename T>

class GraphNode;

 

Posted by 최우림 -=HaeJuK=-
Study/Effective C++2010.03.08 10:25

인터페이스 상속과 구현 상속은 무엇을 의미 하는가?
기본 클래스의 멤버 함수의 상속 방법을 두고 하는 말이다.

인터페이스 상속과 구현 상속의 차이점은 무엇인가?
인터페이스 상속이란, 순수 가상 함수의 상속을 말한다.
구현 상속이란, 가상 함수와 비가상 함수의 상속을 말한다.

 

각 함수들은?
순수 가상 함수 : 가상함수 이나, 함수의 정의부분이 없고, 선언 부분만 있는 함수

가상 함수 : 파생 클래스에서 가상함수를 받는 함수가 없다면, 기본 클래스 함수가 호출되고
                있다면, 파생 클래스의 가상 함수를 호출시켜주는 매체가 되는 함수

비 가상 함수 : 일반 멤버 함수,


class Shape{

Public:

Virtual void draw() const = 0;

Virtual void error(const std::string& msg);

Int objected() const;

……..

};

Class Rectangle : public: shape{ …. };

Class Ellipse : public: Shape { ….. };

 

1. draw 함수는 암시적인 표시 장치에 현재의 객체를 그린다. (순수 가상함수)

2. error 함수는 다른 멤버 함수들이 호출하는 함수로, 이들이 에러를 보고할 필요가 있을 때 사용된다.(단순(비순수)가상함수)

3. objectID 함수는 주어진 객체에 붙는 유일한 정수 식별자는 반환한다.(비가상함수)

 

 

 

 

 

 

Class Shape{

Public:

    Virtual void draw( ) const = 0;

    ……

};

è     가상함수인 draw함수의 특징은….

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

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

à 단순 가상함수를 선언하는 목적은 파생 클래스로 하여금 함수의 인터페이스뿐만 아니라 그 함수의 기본 구현도 물려받게 하자는 것입니다.

 

Class Shape{

Public:

    Int objected( ) const;

};

 

1 비 가상 함수로 되어있다는 것은, 이 함수는 파생 클래스에서 다른 행동이 일어날 것으로 가정하지 않았다는 뜻이다.

2. 비가상 멤버 함수는 클래스 파생에 상관없이 변하지 않는 동작을 지정하는데 쓰인다.

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

 



각 함수들의 사용 법은?
순수 가상 함수 : 모든 파생 클래스마다 동일한 역활을 하는 고유의 동작을 필요  하게 될 때
가상 함수 : 모든 파생 클래스마다 동일한 역활을 하는 일반적인 동작을 필요로 하게 될 때
비가상 함수 : 모든 파생 클래스마다 동일한 역활을 하는 절대적인 동작을 필수로 하게 될 때

이것만은 잊지 말자!
1. 인터페이스 상속은 구현 상속과 다릅니다. Public 상속에서, 파생클래스는 항상 기본 클래스의 인터페이스를 모두 물려받습니다.

2. 순수 가상 함수는 인터페이스 상속만을 허용합니다.

3. 단순(비순수) 가상 함수는 인터페이스 상속과 더불어 기본 구현의 상속도 가능하도록 지정합니다.

4. 비가상 함수는 인터페이스 상속과 더불어 필수 구현의 상속도 가하도록 지정합니다.

Posted by 최우림 -=HaeJuK=-

티스토리 툴바