icon
6장 : 함수

함수 오버로딩

지난 장에서 함수를 정의하고 호출하는 방법을 통해 코드를 모듈화하고 재사용하는 중요성을 학습했습니다.

이제 한 걸음 더 나아가, C++이 제공하는 강력한 기능 중 하나인 함수 오버로딩(Function Overloading) 에 대해 알아보겠습니다.

함수 오버로딩은 같은 이름의 함수를 여러 개 정의할 수 있게 해주는 기능입니다.

언뜻 보기에는 혼란스러워 보일 수 있지만, 실제로는 매우 유용하며 코드의 가독성과 유연성을 크게 향상시킵니다.


함수 오버로딩이란 무엇인가?

함수 오버로딩은 하나의 함수 이름으로 서로 다른 매개변수 목록(Parameter List)을 가진 여러 함수를 정의하는 C++의 기능입니다.

컴파일러는 함수가 호출될 때 전달되는 인자의 타입과 개수에 따라 어떤 함수를 호출해야 할지 자동으로 판단합니다.

함수 오버로딩의 규칙: 함수를 오버로딩하려면 다음 조건 중 하나 이상을 만족해야 합니다.

  1. 매개변수의 개수가 다르다.
  2. 매개변수의 타입이 다르다.
  3. 매개변수의 순서가 다르다.

주의: 함수의 반환 타입만 다른 것으로는 오버로딩할 수 없습니다. 컴파일러는 함수를 호출할 때 반환 타입 정보는 사용하지 않기 때문입니다.

함수 오버로딩 예시 - 반환 타입만 다른 경우
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) 규칙으로 인해 여러 함수가 일치하는 경우: 예를 들어 intfloat 매개변수를 가진 함수가 오버로딩되어 있는데, 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와 같이 내부적으로 다른 이름으로 변환됩니다 (실제 이름 맹글링 규칙은 컴파일러마다 다릅니다).

이렇게 변환된 이름에는 함수의 매개변수 타입 정보가 포함되어 있습니다.

그 덕에 컴파일러와 링커는 오버로딩된 함수들을 명확하게 구별하고 올바른 함수를 연결할 수 있습니다.