함수 오버로딩 (Function overloading)
함수 오버로딩(function overloading)은 다른 매개 변수를 가진 같은 이름의 여러 함수를 만들 수 있는 C++의 기능이다. 다음 함수를 보자.
int add(int x, int y)
{
return x + y;
}
이 간단한 함수는 두 개의 정수를 더한다. 그러나 두 개의 부동 소수점 숫자를 더해야 하는 경우는 어떻게 해야 할까? 부동 소수점 매개 변수가 정수로 변환될 때 소수값을 잃게 되므로 위 함수는 적합하지 않다.
이 문제를 해결하는 한 가지 방법은 약간 다름 이름을 여러 함수를 정의하는 것이다.
int addInteger(int x, int y)
{
return x + y;
}
double addDouble(double x, double y)
{
return x + y;
}
일관된 이름을 정의하고, 함수의 모든 이름을 기억한 다음 올바른 이름의 함수를 불러오는 방법이다.
함수 오버로딩(function overloading)은 더 나은 솔루션을 제공한다. 함수 오버로딩을 사용하면 double
매개 변수를 취하는 또 다른 add()
함수를 선언할 수 있다.
double add(double x, double y)
{
return x + y;
}
이제 add()
의 두 가지 버전이 있다.
int add(int x, int y); // integer version
double add(double x, double y); // floating point version
이것이 네이밍 충돌을 일으킬 것으로 예상할 수도 있지만, 그렇지 않다. 컴파일러는 함수 호출에 사용된 인수를 기반으로 호출할 add()
버전을 결정할 수 있다. 두 개의 int를 제공하면 C++은 add(int, int)
를 호출한다. 두 개의 double
을 제공하면 C++은 add(double, double)
을 호출한다. 사실 각 add()
함수가 고유한 매개 변수를 가지고 있는 한 원하는 만큼 많은 오버로드된 add()
함수를 정의할 수 있다.
따라서 매개 변수의 수가 다른 add() 함수를 정의할 수도 있다.
int add(int x, int y, int z)
{
return x + y + z;
}
이 add()
함수는 2개 대신 3개의 매개 변수를 갖지만 다른 버전은 add()
와 매개 변수가 다르므로 유효한 함수다.
Function return types are not considered for uniqueness
함수의 반환 타입은 함수 오버로딩에서 고려되지 않는다. (함수는 인수에만 기반하여 호출된다. 반환 값이 포함된 경우 함수의 어떤 버전이 호출되었는지 쉽게 알 수 있는 구문 방식이 없다)
난수를 반환하는 함수를 작성하려고 하지만 int를 반환하는 버전과 double을 반환하는 다른 버전이 필요한 경우를 생각해보자. 아래와 같은 유혹에 빠질 수 있다.
int getRandomValue();
double getRandomValue();
그러나 컴파일러는 오류를 발생시킨다. 위 두 함수는 동일한 매개 변수(:없음)를 가지므로 결과적으로 두 번째 getRandomValue()
는 첫 번째 함수를 잘못된 재선언으로 처리된다.
이 문제를 해결하는 가장 좋은 방법은 함수에 서로 다른 이름을 지정하는 것이다.
int getRandomInt();
double getRandomDouble();
또 다른 방법은 함수가 void를 반환하도록 하고 반환 값을 호출자에게 out 참조 매개 변수로 전달하도록 하는 것이다.
void getRandomValue(int& out);
void getRandomValue(double& out);
이러한 함수에는 다른 매개 변수가 있으므로 고유한 것으로 간주된다.
Typedefs are not distinct
typedef
선언은 새로운 타입을 만드는 것이 아니므로 아래 Print()
함수의 두 선언은 동일하게 간주된다.
typedef char* string;
void print(string value);
void print(char* value); // Compile Error
How function calls are matched with overloaded functions
오버로드(overload)된 함수를 호출하면 다음 세 가지 결과 중 하나가 발생한다.
- 일치하는 함수가 있다. (호출이 특정 오버로드된 함수로 해석된다.)
- 일치하는 함수가 없다. (오버로드된 함수 중에 인수가 일치하는 함수가 없다.)
- 모호한 함수가 있다. (하나 이상의 오버로드된 함수와 인수가 일치한다.)
오버로드된 함수가 호출되면 C++은 다음 프로세스를 통해 호출할 함수의 버전을 결정한다.
1) 먼저 C++은 정확하게 일치하는 함수를 찾으려고 한다. 이것은 실제 인수가 오버로드된 함수 중 하나의 매개 변수 타입과 정확히 일치하는 경우다.
void print(char* value);
void print(int value);
print(0); // exact match with print(int)
0은 기술적으로 print(char*)
와 일치할 수 있지만 print(int)
와 정확하게 일치한다.
2) 정확히 일치하는 항목이 없으면 C++은 승격(promotion)을 통해 일치하는 함수를 찾으려고 한다. 이것은 '05.01 - 암시적 형 변환'에서 다룬 적이 있다.
char
, unsigned char
및 short
가 int
로 승격된다.
unsigned short는 int
의 크기에 따라 int
또는 unsigned int
로 승격된다.
float
은 double
로 승격된다.
- 열거형(
enum
)은 int
로 승격된다.
void print(char* value);
void print(int value);
print('a'); // promoted to match print(int)
위 프로그램의 경우, print(char)
가 없으므로 char
'a'는 int
로 승격되고 print(int)
와 일치한다.
3) 승격(promotion)이 불가능한 경우 C++은 표준 변환을 통해 일치하는 항목을 찾으려고 한다.
- 숫자 타입은 다른 숫자 타입으로 변환된다. (Ex.
int
->float
)
- 열거형(
enum
)은 위에서 말한 숫자 공식과 같다. (Ex. enum
->int
->float
)
- 0은 포인터 타입 및 숫자 타입과 일치한다.(Ex.
0
->char*
or 0
->float
)
- 포인터는 void 포인터와 일치한다.
struct Employee; // defined somewhere else
void print(float value);
void print(Employee value);
print('a'); // 'a' converted to match print(float)
이 경우 print(char)
및 print(int)
가 없으므로 'a'는 float
으로 변환되어 print(float)
와 일치한다.
4) 마지막으로, C++은 사용자 정의 변환을 통해 일치하는 함수를 찾는다. 아직 클래스를 배우지는 않았지만, 클래스는 해당 클래스의 객체와 암시적으로 적용될 수 있는 다른 타입으로 변환을 정의할 수 있다. 예를 들어 클래스 X의 사용자 정의 변환을 int
로 정의할 수 있다.
class X; // with user-defined conversion to int
void print(float value);
void print(int value);
X value; // declare a variable named value of type class X
print(value); // value will be converted to an int and matched to print(int)
value는 클래스 X 타입이지만 사용자가 정의한 int
로 변환하므로 print(value)
는 print(int)
로 결정된다.
나중에 클래스를 공부할 때 클래스의 사용자 정의 변환을 수행하는 방법을 자세히 설명할 예정이다.
Ambiguous matches
모든 오버로드된 함수가 고유한 매개 변수를 가져야 하는 경우 호출이 둘 이상과 일치하면 어떻게 될까?
void print(unsigned int value);
void print(float value);
print('a');
print(0);
print(3.14159);
위에서 print('a')
의 경우 C++은 정확하게 일치하는 함수를 찾을 수 없다. 'a'를 int
로 승격시키려고 하지만 print(int)
도 없다. 표준 변환을 하면 'a'는 unsigned int
와 float
으로 변환할 수 있다. 이 두 표준 변환으로 인해 모호한 일치가 발생한다.
print(0)
도 비슷하다. 0은 int
이며, print(int)
는 없다. 결국, 표준 변환으로 인해 모호한 일치가 발생한다.
print(3.14159)
는 조금 놀랍다. 모든 리터럴 부동 소수점 값은 f 접미사가 없으면 double
이 된다. 3.14159는 double
이며 print(double)
은 없다. 따라서 표준 변환을 통해 모호한 일치가 발생한다.
모호한 일치(ambiguous match)는 컴파일 타임 오류로 간주된다. 따라서 프로그램을 컴파일하기 전에 모호한 일치를 명확히 해야 한다. 모호한 일치를 해결할 수 있는 몇 가지 방법이 있다.
1) 호출하려고 하는 타입의 매개 변수를 가지는 새로운 오버로드된 함수를 정의한다. 그러면 C++은 정확히 일치하는 함수를 찾을 수 있다.
2) 모호한 인수를 호출할 함수의 타입으로 명시적으로 변환한다. 예를들어, 다음과 같이하면 된다.
int x = 0;
print(static_cast<unsigned int>(x)); // will call print(unsigned int)
3) 인수가 리터럴인 경우 리터럴 접미사를 사용해서 리터럴이 올바른 타입으로 해석되도록 한다.
print(0u); // will call print(unsigned int) since 'u' suffix is unsigned int
Matching for functions with multiple arguments
인수가 여러 개인 경우 C++은 일치하는 규칙을 차례로 각 인수에 적용한다.
그러한 함수가 발견된 경우, 그 함수는 가장 일치하는 함수다. 그러나 그러한 함수를 찾을 수 없는 경우, 호출은 모호함(또는 불일치)으로 간주된다.
#include <iostream>
void fcn(char c, int x)
{
std::cout << 'a';
}
void fcn(char c, double x)
{
std::cout << 'b';
}
void fcn(char c, float x)
{
std::cout << 'c';
}
int main()
{
fcn('x', 4);
}
위의 프로그램에서 모든 함수는 첫 번째 인수와 정확히 일치한다. 그리고 fcn(char, int)
는 두 번째 매개 변수와 정확히 일치하지만 다른 함수는 변환이 필요하다. 그래서 fcn(char, int)
가 가장 일치하는 함수다.
Rule: 프로그램을 단순화 하려면 함수 오버로딩을 사용하자.
번역: 이 포스트의 원문은 http://www.learncpp.com/cpp-tutorial/76-function-overloading/ 입니다.