본문 바로가기

Effective C++/6. 상속, 그리고 객체 지향 설계

항목 37: 어떤 함수에 대해서도 상속받은 기본 매개변수 값은 절대로 재정의하지 말자

C++에서 상속받을 있는 함수의 종류

  • 비가상 함수 : 절대로 재정의하면 안된다.
  • 가상 함수 : '기본 매개변수 값을 가진 가상 함수를 상속하는 경우' 재정의하면 안된다.

 

안될까? 가상 함수는 동적으로 바인딩(지연 바인딩,late binding)되고, 기본 매개변수 값은 정적으로 바인딩(선행 바인딩,early binding),되기 때문이다.


  • 객체의 정적 타입(static type) : 우리가 작성한 선언문 통해 객체가 갖는 타입

 

어떤 클래스 계통을 예로 들어보자.

 

 

 

ps, pc, pr 모두 Shape* 선언되어 있기 때문에, 정적타입은 그냥 모두 Shape*이다. 하지만 이들이 진짜로 가르키는 대상은 모두 다르다.

 

 

  • 객체의 동적 타입(dynamic type) : 현재 객체가 진짜로 무엇이냐에 따라 결정되는 타입

' 객체가 어떻게 동작할 것이냐' 가리키는 타입

 

따라서 다음과 같이 것이다.

 

 

그리고 가상 함수는 동적으로 바인딩되기 때문에, 호출이 일어난 객체의 동적 타입 따라 어떤 함수가 호출될지가 결정된다.

 

 

 

근데 여기서 '기본 매개변수 값이 설정된' 가상 함수로 오게되면 뭔가 복잡해진다. 이유는 앞에서도 말했지만 가상 함수는 동적으로 바인딩 <-> 기본 매개변수는 정적으로 바인딩되어 있기 때문이다.

 

=> 파생 클래스에서 정의 가상 함수를 호출하면서 기본 클래스에 정의된 기본 매개변수 값을 사용하는 경우가 생길 있다!

 

 

  • pr 동적 타입 : Rectangle* -> Rectangle::draw() 호출
  • pr 정적 타입 : Shape* -> 호출에 쓰이는 기본 매개변수 값을 Shape 클래스에서 가져온다.

 

pr 포인터가 아니라 참조자라도 똑같은 문제가 발생한다.

중요한 점은 draw() 가상 함수이고, 기본 매개변수 값들 하나가 파생클래스에서 재정의되는 순간 문제가 발생할 있다는 것이다.


어째서 C++에선 이러한 것을 막지 않았을까? 기본 매개변수를 동적으로 바인딩하면 안되나?

=> 런타임 효율때문이다.

 

만약 함수의 기본 매개변수가 동적으로 바인딩된다면, 프로그램 실행 중에 가상 함수의 기본 매개변수 값을 결정할 방법을 컴파일러쪽에서 마련해줘야 한다. 아무래도 이런 방법은 정적 바인딩보다 느리고 복잡할 밖에 없다. 그래서 속도 + 구현 간편성를 위해 정적 바인딩을 기본으로 하는 것이다.

 

혹시 기본 매개변수 값을 재정의하되, 똑같은 값으로 재정의하면 괜찮지 않을까…?

 

 

일단 코드 중복이 맘에 들지 않는다. 좋은 것은, 코드 중복 + Dependancy(의존성)까지 걸려 있다는 점이다. 만약 Shape 클래스에서 기본 매개변수 값이 변하기라도 하면 아까와 같은 문제를 야기할 것이다. 문제를 해결할려면 결국 값을 반복하고 있는 파생 클래스는 모두 값을 바꿔야 것이다.

 

그럼 어떻게 해야 할까?

=> 어쩔 없이 다른 설계 방식을 알아봐야 한다.

 

항목 35 보면 가상 함수 대신에 있는 방법들이 개가 소개되어 있다. 이들 하나를 써서 해결해보자.

 

비가상 인터페이스(non-virtual interface) 관용구(NVI 관용구) 써보자.

 

  1. 파생 클래스에서 재정의할 있는 가상 함수를 private 함수로 둔다.
  2. 가상 함수를 호출하는 public 비가상 함수를 기본 클래스에 만들어 둔다.

 

 

 비가상 함수는 파생 클래스에서 절대 오버라이드 되면 안되기 때문에(항목 36 참고) 위와 같이 설계하면 draw() color 매개변수에 대한 기본값을 깔끔하게 Red 고정시킬 있다.

 


  • 상속받은 기본 매개변수 값은 절대로 재정의해서는 안된다. 왜냐하면 기본 매개변수 값은 정적으로 바인딩되는 반면, 가상 함수(우리가 오버라이드할 있는 유일한 함수) 동적으로 바인딩되기 때문이다.