[C++] 항목 33: 상속된 이름을 숨기는 일은 피하자
카테고리: Cpp
태그: Cpp
이 글은 아래의 책을 자세히 정리한 후, 정리한 글을 GPT에게 요약을 요청하여 작성되었습니다.
이펙티브 C++ 제3판, 스콧 마이어스 저자, 곽용재 번역
📦 6. 상속, 그리고 객체 지향 설계
👉🏻 항목 33: 상속된 이름을 숨기는 일은 피하자
✅ 유효범위(scope)의 기본 개념
int x; // 전역 변수
void someFunc() {
double x; // 지역 변수
cin >> x; // 지역 변수 x를 가리킨다.
}
지역 변수가 전역 변수의 이름을 가리는 것처럼, 상속에서도 동일한 원리가 적용된다.
✅ 상속에서의 유효범위 탐색
class Base {
private:
int x;
public:
virtual void mf1() = 0;
virtual void mf2();
void mf3();
...
};
class Derived : public Base {
public:
virtual void mf1();
void mf4() {
...
mf2();
...
};
...
};
유효범위 탐색 순서:
- Derived 내에서 Base의 멤버 함수들을 사용할 수 있다
- Derived 내의 mf4() 함수에서 mf2 호출 시:
- mf4 함수의 유효범위 내부를 먼저 찾는다
- Derived 클래스의 유효범위 내부를 찾는다
- Base 클래스의 유효범위 내부를 찾는다
- 발견 후, 탐색 종료
- 만약 위의 상황에서도 못 찾았다면 전역 유효범위까지 간다
⚠️ 문제 상황: 이름 가림(Name Hiding)
class Base {
private:
int x;
public:
virtual void mf1() = 0;
virtual void mf1(int);
virtual void mf2();
void mf3();
void mf3(double);
...
};
class Derived : public Base {
public:
virtual void mf1();
void mf3();
void mf4();
...
};
int main() {
Derived d;
int x;
...
d.mf1(); // ✅ Derived::mf1 호출
d.mf1(x); // ❌ 에러! Derived::mf1가 Base::mf1을 가림
d.mf2(); // ✅ Base::mf2 호출
d.mf3(); // ✅ Derived::mf3 호출
d.mf3(x); // ❌ 에러! Derived::mf3가 Base::mf3를 가림
}
핵심 문제:
- 기본 클래스(Base)에 있는 함수들 중 mf1 및 mf3는,
파생 클래스(Derived)의 mf1 및 mf3에 의해 가려진다 - 이름이 같은 함수들의 매개변수 타입 여부에도 상관하지 않는다
- 이름이 같은 함수들의 가상/비가상 함수 여부에도 상관하지 않는다
동작 이유:
라이브러리 혹은 응용프로그램 프레임워크를 이용하여 파생 클래스를 만들 때,
멀리 떨어진 기본 클래스로부터 오버로드 버전을 상속시키는 경우를 막기 위해서이다.
✅ 해결 방법 1: using 선언
class Base {
private:
int x;
public:
virtual void mf1() = 0;
virtual void mf1(int);
virtual void mf2();
void mf3();
void mf3(double);
...
};
class Derived : public Base {
public:
// Base의 mf1, mf3을 이름으로 가진 것들을
// Derived의 유효범위에서 볼 수 있도록,
// using를 사용하고, public 멤버로 만든다.
using Base::mf1;
using Base::mf3;
virtual void mf1();
void mf3();
void mf4();
...
};
int main() {
Derived d;
int x;
...
d.mf1(); // ✅ Derived::mf1 호출
d.mf1(x); // ✅ Base::mf1 호출
d.mf2(); // ✅ Base::mf2 호출
d.mf3(); // ✅ Derived::mf3 호출
d.mf3(x); // ✅ Base::mf3를 호출
}
핵심:
- 가려진 이름은 using 선언을 써서 끄집어낼 수 있다
- 기본 클래스(Base)에 오버로드된 함수가 있고, 오버라이드하고 싶다면, 각 이름에 대해 using 선언을 해주어야 한다
- using 선언을 public 영역에 해준 이유는 기본 클래스의 public 영역에 있는 이름들은 파생 클래스에서도 public 영역에 있어야 하기 때문이다
✅ 해결 방법 2: 전달 함수(forwarding function)
private 상속에서 특정 오버로드 버전만 상속받고 싶을 때 사용한다.
class Base {
public:
virtual void mf1() = 0;
virtual void mf1(int);
... // 이전과 동일
};
class Derived : private Base {
public:
// 전달 함수
// 암시적으로 인라인 함수가 됨(항목 30 참조)
virtual void mf1()
{ Base::mf1(); }
...
};
int main() {
Derived d;
int x;
d.mf1(); // ✅ Derived::mf1(매개변수 X) 호출
d.mf1(x); // ❌ 에러! Base::mf1() 가림
}
사용 상황:
- Derived가 Base로부터 private 상속을 받음
- Derived는 매개변수가 없는 mf1 버전만을 상속받길 원함
- using 선언으로 해결할 수 없음
→ 그 이름에 해당하는 것들이 모두 상속됨 - 전달 함수(forwarding function)를 사용하면 됨
📌 using 선언 vs 전달 함수
| 특징 | using 선언 | 전달 함수 |
|---|---|---|
| 사용 상황 | public 상속 | private/protected 상속 |
| 상속 범위 | 해당 이름의 모든 오버로드 | 특정 버전만 선택 가능 |
| 코드 간결성 | 간결함 | 함수마다 작성 필요 |
| 인라인 | 자동 | 암시적 인라인 |
🧐 정리
- 파생 클래스의 이름은 기본 클래스의 이름을 가린다.
public 상속에서는 이런 이름 가림 현상은 바람직하지 않다. - 가려진 이름을 다시 보게 하기 위해, using 선언 혹은 전달 함수를 사용할 수 있다.
- 이름 가림은 매개변수 타입이나 가상 함수 여부와 무관하게 발생한다. 같은 이름이면 무조건 가려진다.
- public 상속에서는 using 선언을, private 상속에서 특정 버전만 필요할 때는 전달 함수를 사용하자.
댓글남기기