본문 바로가기

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

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

상속된 이름 => 진짜 상속이 아니라, 유효범위 관련된 이야기이다.

 

 

여기서 cin 전역변수 x 아닌, 지역변수 x 참조한다. 왜냐하면 안쪽 유효범위에 있는 이름이 바깥쪽 유효범위에 있는 이름을 가리기 때문이다.

 

컴파일러가 someFunc() 유효범위 안에서 x라는 이름을 만나면

  1. 자신이 처리하고 있는 유효범위(지역 유효범위, local scope)안에서 같은 이름을 가진 것을 찾는다.
  2. 지역 유효범위 안에 찾는 이름이 있으면 이상 탐색하지 않는다.
  3. 지역 유효범위 안에 찾는 이름이 없으면 전역 유효범위(global scope) 안에서 같은 이름을 가진 것을 찾는다.

 

=> 이름 탐색에 type 관계가 없다!!

=> 겹치는 이름들의 type 같든 안같든, 현재 중요한 것은 x라는 이름의 double x라는 이름의 int 가리는 이다.


  예제를 보고, 진짜 상속에 관련된 이야기를 해보자면,

 

기본 클래스에 속해 있는 것들(맴버 함수, typedef, 데이터 맴버 등등) 파생 클래스 맴버 함수 안에서 참조하는 문장이 있다 생각해보자. 컴파일러는 참조대상을 바로 찾을 있다. ?

  • 기본 클래스에서 선언된 것은 파생 클래스가 모두 물려받는다.
  • 파생 클래스의 유효범위가 기본 클래스의 유효범위 안에 중첩되어 있다.

 

 

이야기는 전부 이름(identifier) 관련된 이야기 이다.

 

그림을 보면 아까 someFunc()에서 했던 얘기들이 생각난다. 완벽히 동일한 구조이다.

 

만약 파생 클래스 안에서 어떤 이름을 만나면

  1. 자신이 처리하고 있는 유효범위(파생 클래스 유효범위)안에서 같은 이름을 가진 것을 찾는다.
  2. 파생 클래스 유효범위 안에 찾는 이름이 있으면 이상 탐색하지 않는다.
  3. 파생 클래스 유효범위 안에 찾는 이름이 없으면 기본 클래스 유효범위 안에서 찾는다.
  4. 기본 클래스 유효범위 안에 찾는 이름이 없으면 기본 클래스를 둘러싸고 있는 네임스페이스 유효범위 안에서 찾는다.
  5. 네임스페이스 유효범위 안에 찾는 이름이 없으면 전역 유효범위 안에서 찾는다.

 

, 지역 유효범위 -> 파생 클래스 -> 기본 클래스 -> 네임스페이스 -> 전역 순으로 찾는다.

 

이와 관련되서 예제를 하나 보자면,

 

 

위에서 했던 이름 가리기 규칙은 당연히 여기서도 적용이 된다. Derived 클래스 안에서 보면, Base::mf1(), Base::mf1(int), Base::mf3(); Base::mf3(double) 모두 Derived mf1() mf3() 의해 가려진다.

 

 

 

이름 가리기 규칙에서는 타입, 가상함수 여부 등등 모두 상관없이 이름이 가려진다.

 

이렇게 동작하는데에는 이유가 있기 마련이다. 만약 우리가 어떤 라이브러리 혹은 응용프로그램 프레임워크를 이용하여 파생 클래스를 하나 만든다. 라이브러리에 있는 기본 클래스로부터 오버로드된 것들에 대한 상속을 막겠다는 것이다. 파생 클래스의 것들을 우선시 함으로써, 의도치 않은 기본 클래스의 오버로드 버전을 호출하는 일을 막겠다는 것인데(이것을 일종의 실수로 ), 사실 오버로드 버전을 상속했으면 하는 프로그래머가 많은게 현실이다.

 

사실, public 상속을 쓰면서 오버로드 버전의 상속을 막는 것은 엄연히 is-a 관계 위반이다. public 상속은 기본 클래스의 모든 것들이 파생 클래스에 적용되어야 한다. 그래서 C++ 기본적으로 해버리는 이른바 "상속된 이름 가리기" 무시하고 싶은 경우가 거의 대부분일 것이다.

 

=> using 선언을 통해 가려진 이름을 끄집어낼 있다.

 

 

어떤 기본 클래스를 상속받으려 하는데, 오버로드된 함수가 기본클래스에 들어있고 함수들 몇개만 재정의(오버라이드)하고 싶다면, 이름에 대해 using 선언을 붙여 주어야 한다.

 

예를 들어 Base 상속받을려고 하는데, mf1 기본 클래스에도 있고, mf1 Derived 오버라이드 하고 싶다면 using Base::mf1; 하면 된다는 것이다.

 


기본 클래스가 가진 함수를 전부 상속했으면 하는 것이 아닌 경우가 있긴 하다. 물론 이때는 전부 상속을 하지않을 것이기 때문에 public 상속은 빼놓고 생각해야 한다. 위의 예제에서 using  선언이 파생 클래스의 public 영역에 들어 있어야 하는 이유도 이와 같다. 기본 클래스의 public 영역에 있는 이름들은 파생 클래스에서도 public 있어야 한다.

 

하지만 private 상속(항목 39 참고) 사용한다면 괜찮을 있다. 이런 가정을 해보자.

  1. Derived Base로부터 private 상속이 이루어졌고
  2. Derived 상속했으면 하는 mf1 함수는 매개변수가 없는 버전 하나이다.

 

때는 using 선언으로 매개변수 없는 mf1 버전 하나만 끄집어 있을까? 당연히 없다. 왜냐하면 using 선언을 하면 이름(mf1) 해당하는 모든 것들이 파생 클래스로 내려가 버리기 때문이다. 하나의 이름으로 많이 오버로드 되는 함수 하나만 끄집어서 사용하고 싶다면, 전달 함수(forwarding function) 만들어 놓는 것이 좋을 있다.

 

 

 

 


  • 파생 클래스의 이름은 기본 클래스의 이름을 가린다. public 상속에서는 이런 이름 가림 현상은 바람직하지 않다.
  • 가려진 이름을 다시 있게 하는 방법으로, using 선언 혹은 전달 함수를 있다.