티스토리 뷰

반응형

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

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

상속되는 함수는 동적으로 바인딩 된다., 기본 매개변수는 정적 바인딩된다.
가상 함수는 동적으로 바인딩되지만, 런타임 효율성 때문에 기본 매개변수 값은 정적으로 바인딩된다.
동적 바인딩은 늦은 바인딩(late binding)이라고도 불리는데, 객체가 실행 시 그 순간의 상태에 따라 일어난다.

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

이른 바인딩(early binding) 또는 정적 바인딩(static binding)과 대비되는 말이다.

 

예제

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
//기하학 도형을 나타내는 클래스
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
 
void main()
{
    Shape *ps;                        //정적 타입 = Shape*
    Shape *pc= new Circle;
    Shape *pr= new Rectangle;
}
cs

Ps, pc, pr, 은 모두 Shape에 대한 포인터로 선언되어 각각의 정적 타입도 모두 Shape*입니다.
객체의 동적 타입은 현재 그 객체가 진짜로 무엇이냐에 따라 결정되는 타입입니다.
‘이 객체가 어떻게 동작할 것이냐’를 가리키는 타입이 동적 타입이라고 하겠습니다.
ps의 동적 타입은 없고, Pc의 동적 타입은 Circle* , pr의 동적타입은 Rectangle *입니다

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

1
2
3
4
5
6
7
8
void main()
{    
    Shape *ps;                        //정적 타입 = Shape*
    Shape *pc= new Circle;
    Shape *pr= new Rectangle;
    Ps = pc;     // ps의 동적 타입은 Circle * 으로 바뀜
    Ps = pr;     // ps의 동적 타입은 Rectangle *으로 바뀜
}
cs

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

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

 

1
2
3
4
5
6
7
void main()
{    
    Shape *ps;                        //정적 타입 = Shape*
    Shape *pc= new Circle;
    Shape *pr= new Rectangle;
    Pr -> draw();        //Rectangle :: draw(Shape::Red) 호출
}
cs

Pr의 동적 타입은 Rectangle* 이므로 가상 함수는 Rectangle의 것이다.
Rectangle::draw 함수의 초기값은 Green이지만 pr의 정적 타입이 Shape*이기 때문에 값을 Shape클래스에서 가져옴.

기본 매개변수 값들 중 하나가 파생 클래스에서 재정의 되면 문제가 생김.

문제점이 발생되는 이유는 런타임 효율이라는 요소 때문입니다.

함수의 기본 매개변수가 동적으로 바인딩된다면 프로그램 실행 중에 가상 함수의 기본 매개변수 값을 결정할 방법을 컴파일러 쪽에서 마련해 주어 야할 것입니다.

이 방법은 컴파일 과정에서 결정하는 현재의 메커니즘보다 느리고 복잡할 것이다.
이러한 이유로 지금 효율 좋은 실행 동작을 누릴 수 있게 된 것입니다

해결책

비가상 인터페이스(Non-Virtual Interface) 관용구(NVI 관용구)를 쓰는 것.
이 방법은 파생 클래스에서 재정의할 수 있는 가상 함수를 private 멤버로 두고,
가상 함수를 호출하는 public 비가상 함수를 기본 클래스에 만들어 두는 것이다.

비가상 함수가 기본 매개변수를 지정하도록 할 수 있고, 이 비가상 함수의 내부에서 진짜 일을 맡은 가상 함수를 호출하게 됨.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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;          //기본 매개변수 값이 없음
};
cs

 

결론

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

반응형
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
«   2024/03   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
31
글 보관함