함수 오버로딩
지난 장에서 함수를 정의하고 호출하는 방법을 통해 코드를 모듈화하고 재사용하는 중요성을 학습했습니다.
이제 한 걸음 더 나아가, C++이 제공하는 강력한 기능 중 하나인 함수 오버로딩(Function Overloading) 에 대해 알아보겠습니다.
함수 오버로딩은 같은 이름의 함수를 여러 개 정의할 수 있게 해주는 기능입니다.
언뜻 보기에는 혼란스러워 보일 수 있지만, 실제로는 매우 유용하며 코드의 가독성과 유연성을 크게 향상시킵니다.
함수 오버로딩이란 무엇인가?
함수 오버로딩은 하나의 함수 이름으로 서로 다른 매개변수 목록(Parameter List)을 가진 여러 함수를 정의하는 C++의 기능입니다.
컴파일러는 함수가 호출될 때 전달되는 인자의 타입과 개수에 따라 어떤 함수를 호출해야 할지 자동으로 판단합니다.
함수 오버로딩의 규칙: 함수를 오버로딩하려면 다음 조건 중 하나 이상을 만족해야 합니다.
- 매개변수의 개수가 다르다.
- 매개변수의 타입이 다르다.
- 매개변수의 순서가 다르다.
주의: 함수의 반환 타입만 다른 것으로는 오버로딩할 수 없습니다. 컴파일러는 함수를 호출할 때 반환 타입 정보는 사용하지 않기 때문입니다.
int add(int a, int b) { return a + b; }
double add(int a, int b) { return (double)a + b; } // 컴파일 오류! 매개변수 목록이 동일함.
함수 오버로딩의 필요성
함수 오버로딩은 실제 프로그래밍에서 다음과 같은 상황에 유용하게 사용됩니다.
- 동일한 개념의 다른 데이터 타입 처리: 예를 들어, 두 수를 더하는 함수를 만들 때,
int
형 정수뿐만 아니라double
형 실수도 더할 수 있게 하고 싶을 수 있습니다. 오버로딩이 없다면addInt
,addDouble
과 같이 별개의 함수 이름을 사용해야 합니다. - 다양한 인자 개수 지원: 어떤 작업을 수행하는 데 인자가 한두 개만 필요할 수도 있고, 더 많은 정보가 필요할 수도 있습니다.
- 코드의 일관성 및 가독성 향상: 동일한 작업을 수행하는 함수에 같은 이름을 부여함으로써 코드의 의미론적인 일관성을 유지하고 가독성을 높일 수 있습니다. 사용자 입장에서는
add
라는 이름만으로도 덧셈 기능을 떠올릴 수 있습니다.
함수 오버로딩 예시
정수형 덧셈과 실수형 덧셈을 모두 처리하는 add
함수를 오버로딩해 봅시다.
#include <iostream>
#include <string>
// 1. 두 정수를 더하는 함수
int add(int a, int b) {
std::cout << "int add(int, int) 호출" << std::endl;
return a + b;
}
// 2. 두 실수를 더하는 함수 (매개변수 타입 다름)
double add(double a, double b) {
std::cout << "double add(double, double) 호출" << std::endl;
return a + b;
}
// 3. 세 정수를 더하는 함수 (매개변수 개수 다름)
int add(int a, int b, int c) {
std::cout << "int add(int, int, int) 호출" << std::endl;
return a + b + c;
}
// 4. 문자열을 이어 붙이는 함수 (의미는 다르지만, 이름은 같음)
std::string add(std::string s1, std::string s2) {
std::cout << "std::string add(std::string, std::string) 호출" << std::endl;
return s1 + s2;
}
int main() {
// 호출 시 인자의 타입과 개수에 따라 적절한 함수가 선택됩니다.
std::cout << "결과: " << add(5, 7) << std::endl; // int add(int, int) 호출
std::cout << "결과: " << add(3.5, 2.1) << std::endl; // double add(double, double) 호출
std::cout << "결과: " << add(1, 2, 3) << std::endl; // int add(int, int, int) 호출
std::cout << "결과: " << add("Hello", "World") << std::endl; // std::string add(std::string, std::string) 호출
return 0;
}
위 예시에서 add
라는 동일한 이름의 함수가 4가지 버전으로 오버로딩되어 있습니다.
컴파일러는 main
함수에서 add
를 호출할 때 전달되는 인자의 타입(int
, double
, std::string
)과 개수(두 개, 세 개)를 분석하여 가장 적절한 함수 버전을 찾아 호출합니다.
이것을 함수 오버로드 해석(Function Overload Resolution) 이라고 합니다.
매개변수 타입의 순서가 다르면 오버로딩이 가능합니다.
#include <iostream>
#include <string>
// 문자열과 정수를 출력하는 함수
void printValue(std::string text, int number) {
std::cout << "텍스트: " << text << ", 숫자: " << number << std::endl;
}
// 정수와 문자열을 출력하는 함수 (매개변수 순서 다름)
void printValue(int number, std::string text) {
std::cout << "숫자: " << number << ", 텍스트: " << text << std::endl;
}
int main() {
printValue("Age", 30); // void printValue(std::string, int) 호출
printValue(100, "Score"); // void printValue(int, std::string) 호출
return 0;
}
오버로드 해석의 모호성
컴파일러가 호출된 함수에 대해 어떤 오버로드된 함수를 선택해야 할지 명확하게 결정할 수 없는 경우를 모호성(Ambiguity) 이라고 합니다.
이런 경우 컴파일 오류가 발생합니다.
모호성이 발생하는 흔한 경우
- 자동 형 변환(Implicit Type Conversion) 규칙으로 인해 여러 함수가 일치하는 경우: 예를 들어
int
와float
매개변수를 가진 함수가 오버로딩되어 있는데,double
값을 인자로 전달하면int
로 변환할지float
로 변환할지 모호해질 수 있습니다. - 기본 인자(Default Arguments)와 오버로딩이 함께 사용될 때: 기본 인자를 가진 함수와 다른 오버로딩된 함수가 동일한 호출 형태를 만들 수 있는 경우.
#include <iostream>
void print(int value) {
std::cout << "Int: " << value << std::endl;
}
void print(double value) {
std::cout << "Double: " << value << std::endl;
}
int main() {
print(10); // OK: int print(int) 호출
print(10.0); // OK: double print(double) 호출
// print(10L); // L은 long int 리터럴. int와 double 중 어느 것으로 자동 형 변환할지 모호!
// 컴파일러에 따라 경고 후 특정 함수 선택 또는 오류 발생
// 일반적으로 long은 int로도 double로도 변환 가능하므로 모호성 발생.
// 명시적으로 print((int)10L) 또는 print((double)10L) 처럼 캐스팅해야 함.
// print(true); // bool은 int로도 float/double로도 변환 가능하므로 모호성 발생!
// 컴파일 오류! (참고로, C++ 표준에 따르면 bool은 int로 변환하는 것이 더 나은 일치로 간주될 수 있습니다.)
return 0;
}
이러한 모호성을 피하려면, 함수 호출 시 인자의 타입을 명확하게 지정하거나 (명시적 형 변환), 오버로딩된 함수들의 매개변수 목록을 서로 겹치지 않도록 설계해야 합니다.
main
함수는 오버로딩될 수 없다
main
함수는 프로그램의 시작점이며, C++ 표준에 의해 정의된 특별한 함수입니다. main
함수는 오버로딩될 수 없습니다. int main()
또는 int main(int argc, char* argv[])
와 같은 특정 형태만 허용됩니다.
함수 오버로딩과 이름 맹글링
컴파일러는 오버로딩된 함수들을 구별하기 위해 내부적으로 함수 이름을 변경합니다.
이 과정을 이름 맹글링(Name Mangling) 또는 이름 장식(Name Decoration) 이라고 합니다.
예를 들어 add(int, int)
는 _Z3addii
와 같이, add(double, double)
은 _Z3adddd
와 같이 내부적으로 다른 이름으로 변환됩니다 (실제 이름 맹글링 규칙은 컴파일러마다 다릅니다).
이렇게 변환된 이름에는 함수의 매개변수 타입 정보가 포함되어 있습니다.
그 덕에 컴파일러와 링커는 오버로딩된 함수들을 명확하게 구별하고 올바른 함수를 연결할 수 있습니다.