728x90
반응형

변수의 유효 범위

int x; // 전역변수
void func()
{
  double x; // 지역변수
  cin >> x; // 지역변수 x에 값을 읽어 넣는다.
}

컴파일러는 자신이 처리하고 있는 지역 유효 범위(local scope)를 뒤져서 같은 이름을 가진 것이 있는지 알아본다. 그리고 찾았다면 이외의 유효 범위에 대해서는 더 이상 탐색하지 않는다. 그리고 지역 유효 범위 안에 찾는 이름이 없다면 전역 유효 범위(global scope) 안에서 같은 이름을 가진 것을 찾는다.

 

상속과 변수의 유효 범위

class Base
{
private:
  int x;
public:
  virtual void f1() = 0;
  virtual void f1(int); // 파생 클래스에 의해 가려짐
  virtual void f2();
  void f3();
  void f3(double);      // 파생 클래스에 의해 가려짐
};

class Derived: public Base
{
public:
  virtual void f1();
  void f3();
  void f4();
}

Derived d;
int x;

d.f1();  // Derived::f1을 호출
d.f1(x); // 에러 Derived::f1이 Base::f1을 가림

d.f2();  // Base::f2를 호출

d.f3();  // Derived::f3을 호출
d.f3(x); // 에러 Derived::f3이 Base::f3을 가림

기본 클래스에 있는 함수들 중 f1, f3이라는 이름이 붙은 것은 모두 파생 클래스에 있는 f1, f3에 의해 가려진다. 따라서 Base::f1과 Base::f3은 Derived가 상속한 것이 아니게 된다.

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

1. 자신이 처리하고 있는 유효 범위(파생 클래스 유효 범위) 안에서 같은 이름을 가진 것을 찾는다.

2. 파생 클래스 유효 범위 안에 찾는 이름이 있으면 더 이상 탐색하지 않는다.

3. 파생 클래스 유효 범위 안에 찾는 이름이 없으면 기본 클래스 유효 범위 안에서 찾는다.

4. 기본 클래스 유효 범위 안에 찾는 이름이 없으면 기본 클래스를 둘러싸고 있는 네임스페이스 유효 범위 안에서 찾는다.

5. 네임스페이스 유효 범위 안에 찾는 이름이 없으면 전역 유효 범위 안에서 찾는다.

 

즉, 지역 유효 범위 -> 파생 클래스 -> 기본 클래스 -> 네임스페이스 -> 전역 순으로 찾는데 위와 같은 경우 지역에 네임이 있기 때문에 기본 클래스의 함수가 가려지는 것이다.

 

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

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

 

하지만 상속된 이름 가리기를 무시하고 싶은 경우가 있을 수 있는데 이런 경우 사용할 수 있는 방법들에 대해서 알아보자.

 

이름 가리기를 무시하는 방법

using 선언

class Derived: public Base
{
public:
// Base에 있는 것들 중 mf1과 mf3을 이름으로 가진 것들을 Derived의 유효범위에서 볼 수 있도록 (또 public 멤버로) 만들자.
  using Base::f1;
  using Base::f3;

  virtual void f1();
  void f3();
  void f4();
}

이렇게 가려진 이름을 using 선언을 통해 꺼낼 수 있다.

즉, 기본 클래스로부터 상속을 받으려고 하는데 오버로드된 함수가 그 클래스에 들어있고 이 함수들 중 몇 개만 오버라이드 하고 싶다면 각 이름에 대해 using선언을 붙여주면 된다.

 

전달 함수

using 선언을 하면 해당되는 것들이 모두 파생 클래스로 내려가는데 기본 클래스가 가진 함수를 전부 상속했으면 하지 않는 경우도 존재할 것이다.

이 경우 아래처럼 전달 함수를 만들어 놓는 것을 통해 해결할 수 있다.

class Derived: private Base
{
public:
  virtual void f1() { Base::f1(); } // 전달 함수. 암시적으로 인라인 함수가 된다.
}

...
Derived d;
int x;

d.mf1(); // Derived::mf1() 을 호출한다.
d.mf1(x); // 에러! Base::mf1()은 가려져 있다.

하지만 이 경우 is-a관계가 깨지기 때문에 public 상속은 함께 놓고 생각하면 안 된다. 하지만 private 상속을 사용한다면 이 경우가 말이 될 것이다.

 

요약

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