C++에서는 이름은 같지만 서로 다른 버전의 함수를 정의하는 함수 오버로딩(Function Overloading)이 가능합니다. 흥미로운 점은 멤버 함수 뒤에 붙는 const 키워드도 오버로딩 구분 기준이 된다는 것입니다. 즉, 일반 함수와 상수(const) 타입의 함수를 같은 이름으로 공존시킬 수 있습니다.
상수 함수와 일반 함수가 어떻게 구분되어 호출되는지, 아래 예제 프로그램과 출력 결과를 통해 자세히 알아보겠습니다.
예제 1: 객체의 상수 여부에 따른 함수 호출
#include <iostream>
using namespace std;
class my_class {
public:
void my_func() const {
cout << "Calling the constant function" << endl;
}
void my_func() {
cout << "Calling the normal function" << endl;
}
};
int main() {
my_class obj;
const my_class obj_c;
obj.my_func();
obj_c.my_func();
}출력 결과
Calling the normal function Calling the constant function
실행 결과를 보면 알 수 있듯이, 일반 객체로 함수를 호출하면 일반 함수가 선택되고, 상수 객체로 호출하면 상수 함수가 선택됩니다. 컴파일러는 호출 시점의 객체가 const인지 아닌지를 판단하여 가장 적합한 오버로딩 버전을 찾아줍니다.
예제 2: 값 타입 매개변수의 const 오버로딩은 불가능
그렇다면 매개변수에 const를 붙인 경우도 마찬가지일까요? 두 개의 오버로딩된 메서드가 값을 전달받는 매개변수를 가지고 있는데, 한쪽은 일반 매개변수이고 다른 쪽은 상수 매개변수라면 컴파일 오류가 발생합니다.
#include <iostream>
using namespace std;
class my_class {
public:
void my_func(const int x) {
cout << "Calling the function with constant x" << endl;
}
void my_func(int x){
cout << "Calling the function with x" << endl;
}
};
int main() {
my_class obj;
obj.my_func(10);
}출력 결과
[Error] 'void my_class::my_func(int)' cannot be overloaded [Error] with 'void my_class::my_func(int)'
값(value)으로 전달되는 매개변수의 경우, 함수 내부에서 해당 값은 복사본이기 때문에 const 여부가 호출자 입장에서 아무런 의미가 없습니다. 따라서 컴파일러는 my_func(int)와 my_func(const int)를 동일한 시그니처로 간주하여 중복 정의 오류를 발생시킵니다.
예제 3: 포인터·참조 타입 매개변수는 const 오버로딩 가능
반면, 인수가 포인터(pointer)나 참조(reference) 타입이라면 이야기가 달라집니다. 이 경우에는 실제 원본 데이터를 가리키므로 const 여부가 의미를 가지며, 컴파일러도 이를 서로 다른 시그니처로 구분합니다. 따라서 오류 없이 정상적으로 오버로딩됩니다.
#include <iostream>
using namespace std;
class my_class {
public:
void my_func(const int *x) {
cout << "Calling the function with constant x" << endl;
}
void my_func(int *x) {
cout << "Calling the function with x" << endl;
}
};
int main() {
my_class obj;
int x = 10;
obj.my_func(&x);
}출력 결과
Calling the function with x
정리
- 멤버 함수 뒤에 붙는
const는 유효한 오버로딩 구분 기준이며, 상수 객체는 상수 멤버 함수만 호출할 수 있습니다. - 값 타입 매개변수(
intvsconst int)는 시그니처가 동일하게 취급되어 오버로딩이 불가능합니다. - 포인터나 참조 타입 매개변수(
int*vsconst int*)는 시그니처가 다르게 취급되므로 오버로딩이 가능합니다.