[Effective C++] #33_오버 라이딩 문제
Scott Meyers의 "Effective C++" 를 통해, C++ 구현에 필요한 개념들을 이해하고, 기록하기 위함입니다. 해당 항목은 6장 '상속 그리고 객체 지향 설계', 항목 33 "상속된 이름을 숨기는 일은 피하자"에 해당하는 내용입니다.
유효 범위, Scope
int x;
void someFunc() {
double x;
std::cin >> x;
}
먼저, 유효범위에 대해 알아보겠습니다. std::cin을 실행하여 값을 입력받을 "x"변수는 가장 위에 선언한 "int x"일까요, someFunc 함수 내부에 선언된 "double x"일까요? 답은 지역 변수 "double x"입니다. 이유는 안쪽 유효범위, 즉 "void someFunc() {...}" 함수 내부에 정의된 "double x"가 유효 범위 밖에 정의된 "int x"를 가리기 때문입니다. 컴파일러는 someFunc 함수 유효범위 안에서 "x"변수를 만나면, 동일한 유효 범위 안에 같은 이름을 갖는 변수가 존재하는지 체크합니다. 이때, 컴파일러는 someFunc 함수 유효 범위 내부에 "double x"를 찾게 될 것이고, 더 이상 "x"를 찾지 않고 끝날 것입니다. 그럴 필요도 없죠. 앞서 설명한 내용과 함께 책에서 말하는 "이름 가리기"의 의미는 유효범위 안에 존재하는 이름이, 그 밖에 존재하는 동일한 이름을 가린다는 의미가 되겠습니다.
파생 클래스의 유효범위
class 기본클래스 {
public:
virtual void doSomething () = 0;
virtual void doSomething_2 ();
void doSomething_3 ();
...
private:
int x;
};
class 파생클래스: public 기본클래스 {
public:
virtual void doSomething ();
void doSomething_4();
...
};
// 파생 클래스의 멤버함수 doSomething_4를 정의
void 파생클래스::doSomething_4 () {
...
doSomething_2 (); //기본클래스의 멤버 함수 호출
...
}
C++에서, 파생 클래스는 기본 클래스의 멤버 함수를 참조할 수 있으며, 컴파일러는 이 참조 대상을 바로 찾을 수 있습니다. 알다시피 기본 클래스에 선언된 것은 파생 클래스가 모두 상속받기 때문입니다. 위 코드를 살펴보겠습니다. 파생 클래스의 멤버 함수 "doSometing_4"는 내부에서 기본 클래스의 멤버 함수 "doSomething_2"를 호출합니다. 이때, 컴파일러는 "doSomething_2"의 선언문이 들어있는 유효범위를 탐색할 것입니다. 앞서 설명한것 처럼, 유효범위 탐색 과정은 작은 단위의 혹은 가장 가까운 유효범위를 먼저 탐색하고, 다음으로 더 넓은 범위의 유효범위를 탐색하는 순서입니다. 예를 들면, 컴파일러는 "doSomething_2"가 파생 클래스의 유효범위에서 선언 되었는지 체크하고, 그 다음으로 "doSomething_2"가 실제로 선언되어 있는 더 넓은 범위의 기본 클래스의 유효범위를 탐색합니다.
이름 가리기 예제
class 기본클래스 {
public:
virtual void doSomething (int number1);
virtual void doSomething_2 ();
void doSomething_3 (double number2);
...
private:
int x;
};
class 파생클래스: public 기본클래스 {
public:
void doSomething ();
void doSomething_3 (); // doSomething_3 오버로딩
void doSomething_4();
...
};
*************** 사용 예제 ***************
파생클래스 A;
int x;
A.doSomething (); // 문제 없습니다! 파생 클래스의 doSomething 함수가 호출됩니다.
A.doSomething (x); // 에러입니다! 파생클래스::doSomething이 기본클래스::doSomething을 가립니다.
A.doSomething_2 (); // 문제 없습니다! 파생 클래스의 doSomething_2 함수가 호출됩니다.
A.doSomething_3 (); // 문제 없습니다! 파생 클래스의 doSomething_3 함수가 호출됩니다.
A.doSomething_3 (x); // 에러입니다! 파생클래스::doSomething_3이 기본클래스::doSomething_3을 가립니다.
이름 가리기 예제를 코드를 통해 살펴보겠습니다. "doSomething" 함수는 기본클래스의 정수를 매개변수로 받도록 선언되어 있습니다. 더불어, 같은 이름의 "doSomething" 함수가 파생 클래스의 내부에도 선어 되었으며, "파생클래스::doSomething"은 매개변수를 받지 않습니다. 따라서, 위의 코드처럼 파생클래스 객체를 생성하여, 해당 객체의 "doSomething" 혹은 "doSomething_3"를 호출할 경우, 가장 가까운 혹은 가장 작은 유효 범위 내에서 선언된 해당 이름을 갖는 함수들을 호출하게 되어, 더 넓은 범위의 함수들, 즉 기본 클래스에서 선언된 동일한 이름의 함수들이 가려집니다. 그러므로, 파생 클래스 객체의 "doSomething" 함수를 호출하는 경우, 매개 변수를 인자로 받게 되면 오류가 발생합니다. 그렇다면! 부모 클래스의 멤버 함수를 자식 클래스에서 오버 라이딩 혹은 호출할 때, 다른 유효 범위의 동일한 이름을 갖는 멤버 함수들을 모두 효율적으로 사용하는 방법이 무엇이 있을까요? 바로 "using"과 "범위 지정자(::)" 사용입니다.
Using 사용과 범위 지정자
class 기본클래스 {
public:
virtual void doSomething (int number1);
virtual void doSomething_2 ();
void doSomething_3 (double number2);
...
private:
int x;
};
class 파생클래스: public 기본클래스 {
public:
using 기본클래스::doSomething; // 기본클래스 유효범위에 있는 함수들을
using 기본클래스::doSomething_3; // 파생클래스 유효범위에서 볼 수 있도록 만듭니다.
void doSomething ();
void doSomething_3 (); // doSomething_3 오버로딩
void doSomething_4();
...
};
*************** 사용 예제 ***************
파생클래스 A;
int x;
A.doSomething (); // 문제 없습니다! 파생 클래스의 doSomething 함수가 호출됩니다.
A.doSomething (x); // 이제 문제 없습니다! 기본 클래스의 doSomething 함수가 호출됩니다.
A.doSomething_3 (); // 문제 없습니다! 파생 클래스의 doSomething_3 함수가 호출됩니다.
A.doSomething_3 (x); // 이제 문제 없습니다! 기본 클래스의 doSomething 함수가 호출됩니다.
위 코드는 "파생 클래스::doSomething"과 "파생 클래스::doSomething_3"에 이름이 가려져 있던 "기본 클래스::doSomething"와 "기본 클래스::doSomething_3"이 정상적으로 호출되는 것을 보여줍니다. 이때, "using"과 "범위 지정자(::)"를 사용해서, 파생 클래스의 오버 라이딩으로 인해 이름이 가려져 있던 기본 클래스의 멤버 함수들을 파생 클래스 유효 범위 안에 넣어 주었습니다. 결과적으로, "기본 클래스::doSomething"과 "기본 클래스::doSomething_3" 또한 정상적으로 호출되는 것을 알 수 있습니다.
자식 클래스의 이름은 부모 클래스의 이름을 가립니다. Public 상속의 경우 이러한 현상은 피해야 합니다.
오버라이딩으로 가려진 이름들은 using과 범위 지정자를 통해 사용 할 수 있습니다!
'언어 > Effective C++' 카테고리의 다른 글
[Effective C++] #3_const 사용 (0) | 2022.01.05 |
---|---|
[Effective C++] #2_#define, 매크로 사용의 대안 (0) | 2022.01.03 |
[Effective C++] #32_public 상속 (0) | 2022.01.03 |
[Effective C++] #31_파일 사이의 컴파일 의존성 (0) | 2021.12.31 |
[Effective C++] #30_인라인 함수 (0) | 2021.12.30 |