C++에서는 함수 오버로딩(function overloading)을 통해 이름은 같지만 매개변수 구성이 다른 여러 함수를 정의할 수 있습니다. 그런데 기본 클래스(base class)에 오버로드된 형태의 메서드가 존재하고, 파생 클래스(derived class)에서 그중 하나만 재정의하면 예상치 못한 문제가 발생합니다.
바로 파생 클래스에서 해당 함수 이름으로 접근할 때, 기본 클래스에 정의되어 있던 모든 오버로드 버전이 함께 가려지는(name hiding) 현상입니다.
왜 이런 현상이 발생할까?
C++의 이름 조회(name lookup) 규칙 때문입니다. 컴파일러는 함수 호출 시 파생 클래스의 범위에서 먼저 함수 이름을 검색하는데, 해당 이름을 찾는 순간 더 이상 기본 클래스까지 내려가 탐색하지 않습니다. 따라서 파생 클래스에 존재하는 함수만 후보로 남고, 기본 클래스의 나머지 오버로드 버전들은 후보 목록에서 제외됩니다.
예제 코드
#include <iostream>
using namespace std;
class MyBaseClass {
public:
void my_function() {
cout << "This is my_function. This is taking no arguments" << endl;
}
void my_function(int x) {
cout << "This is my_function. This is taking one argument x" << endl;
}
};
class MyDerivedClass : public MyBaseClass {
public:
void my_function() {
cout << "This is my_function. From derived class, This is taking no arguments" << endl;
}
};
main() {
MyDerivedClass ob;
ob.my_function(10);
}파생 클래스에서는 인수 없이 호출되는 my_function()만 재정의했습니다. 그런데 객체에서 my_function(10)처럼 int 인수를 넘겨 호출하면 어떻게 될까요?
컴파일 결과
[Error] no matching function for call to 'MyDerivedClass::my_function(int)' [Note] candidate is: [Note] void MyDerivedClass::my_function() [Note] candidate expects 0 arguments, 1 provided
기본 클래스에 my_function(int)가 분명히 존재하는데도 컴파일 에러가 발생합니다. 파생 클래스의 my_function()이 이름을 차지하면서 기본 클래스의 모든 오버로드 버전이 보이지 않게 된 것입니다.
해결 방법: using 선언 활용하기
이 문제는 파생 클래스 안에서 using 선언을 사용하면 간단히 해결할 수 있습니다. 기본 클래스의 함수들을 파생 클래스 범위로 끌어오면 이름 가림 현상이 사라집니다.
class MyDerivedClass : public MyBaseClass {
public:
using MyBaseClass::my_function; // 기본 클래스의 모든 오버로드 노출
void my_function() {
cout << "This is my_function. From derived class, This is taking no arguments" << endl;
}
};
main() {
MyDerivedClass ob;
ob.my_function(); // 파생 클래스 버전 호출
ob.my_function(10); // 기본 클래스 버전 호출 가능
}using MyBaseClass::my_function; 한 줄만 추가하면 기본 클래스의 오버로드 버전들이 파생 클래스에서도 정상적으로 호출됩니다. 또는 ob.MyBaseClass::my_function(10);처럼 스코프 연산자(::)를 이용해 명시적으로 기본 클래스의 함수를 지정하는 방법도 있습니다.
정리
- 파생 클래스에서 기본 클래스의 오버로드된 함수 중 하나라도 재정의하면, 나머지 오버로드 버전까지 모두 숨겨집니다.
- 이것은 C++의 이름 조회 규칙에 따른 정상적인 동작이며 버그가 아닙니다.
using선언을 사용하면 기본 클래스의 오버로드 함수들을 손쉽게 다시 노출시킬 수 있습니다.