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

업데이트:     Updated:

카테고리:

태그:

이 글은 아래의 책을 자세히 정리한 후, 정리한 글을 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 호출 시:
    1. mf4 함수의 유효범위 내부를 먼저 찾는다
    2. Derived 클래스의 유효범위 내부를 찾는다
    3. Base 클래스의 유효범위 내부를 찾는다
    4. 발견 후, 탐색 종료
  • 만약 위의 상황에서도 못 찾았다면 전역 유효범위까지 간다

⚠️ 문제 상황: 이름 가림(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 상속
상속 범위 해당 이름의 모든 오버로드 특정 버전만 선택 가능
코드 간결성 간결함 함수마다 작성 필요
인라인 자동 암시적 인라인

🧐 정리

  1. 파생 클래스의 이름은 기본 클래스의 이름을 가린다.
    public 상속에서는 이런 이름 가림 현상은 바람직하지 않다.
  2. 가려진 이름을 다시 보게 하기 위해, using 선언 혹은 전달 함수를 사용할 수 있다.
  3. 이름 가림은 매개변수 타입이나 가상 함수 여부와 무관하게 발생한다. 같은 이름이면 무조건 가려진다.
  4. public 상속에서는 using 선언을, private 상속에서 특정 버전만 필요할 때는 전달 함수를 사용하자.

Cpp 카테고리 내 다른 글 보러가기

댓글남기기