
함수 오버로딩
함수 오버로딩이란 다른 매개변수를 가진 같은 이름의 여러 함수를 만드는것이다
int add(int a, int b)
{
return a + b;
}
위와 같은 함수가 있을때 실수를 더해서 return하는 함수가 필요하다면 하나 더 만들어야 할까?
만약 하나 더 만든다면 다른 이름의 함수가 굉장히 많아질 것이다 (유지보수에 좋지 않음)
이럴때 함수 오버로딩을 사용한다, 불필요한 함수의 갯수를 줄일 수 있다
함수 오버로딩의 조건은 매개변수의 개수나 타입(하나라도 다르면 가능)이 달라야 한다, 이때 반환형은 영향을 받지 않는다 (반환 타입만 다르면 오버로딩 불가능, 컴파일러가 함수 호출만 보고 반환형을 알 수 없기 때문에 다른 함수인지 구별할 수 없다)
double add(double a, double b)
{
}
double add(double a, int b)
{
}
//전부 다 가능
컴파일러는 함수의 매개변수의 타입이나 개수가 다르기때문에 서로 다른 함수로 인식하기 때문에 명명충돌이 발생하지 않는다
가장 중요한 점은 컴파일러가 함수들을 별개의 함수로 구별할 수 있어야 한다는 것이다 (매개변수의 타입, 갯수)
컴파일러가 이러한 매개변수 타입, 갯수를 기준으로 적절한 함수를 선택하는걸 Overload Resolution이라고 한다
추가로 함수가 멤버함수라면 함수가 const, volatile, &, &&에 따라서도 오버로딩 구별이 가능해진다
class Knight
{
public:
void foo(int a)
{
}
void foo(int a) const //const객체에서도 호출 가능
{
}
void foo(int a) & //lvalue객체에서만 호출 가능
{
}
void foo(int a) && //rvalue객체에서만 호출 가능
{
}
}
//overloading 가능
그리고 typedef, using을 쓴 타입별칭으로는 타입 구분이 불가능하다
typedef int typedefint;
using usingint = int;
void foo(int a);
void foo(usingint a);
void foo(typedefint a);
//내부적으로는 전부 int이기 때문에 타입 구분이 불가능, overloading X
또한 가변 매개변수인 ...을 사용하여 타입 구분이 가능하다
void foo(int a, int b);
void foo(int a, ...);
foo(10, 20);
foo(10, "hi");
Function Signature
함수 시그니처란 함수 이름, 매개변수의 갯수 및 타입, 멤버 함수 한정자 (const, & 등)을 의미한다, 이때 반환형은 함수 시그니처에 포함되지 않는다
Name Mangling
컴파일러는 함수 오버로딩을 처리하기 위해 Name Mangling을 수행한다
void foo();
void foo(int);
위와 같은 상황에서 컴파일러는 내부적으로 함수 이름을 다르게 처리하여 구별한다
foo_v, foo_i 등으로
Function Overloading Resolution
컴파일러가 함수 호출 처리를 할 때 여러 단계를 거쳐 알맞는 함수를 찾는다, 이러한 과정을 function overloading resolution이라고 한다
1. 정확한 매칭
가장 먼저 컴파일러는 함수 시그니처가 동일한 함수가 있는지 확인한다, 이때 여기서 Trivial Conversion (사소한 변환)도 허용한다
Trivial Conversion에는 non-const -> const, non ref -> ref로의 변환등이 있다
void foo(const int);
void foo(const double&);
foo(1); //int -> const int로 trivial conversion
foo(2.5); //double -> const double&로 trivial conversion
3. 숫자 승격 (Numeric Promotion)
정확한 매칭에서 나온 함수가 없다면 작은 타입을 큰 타입으로 숫자 승격을 시도한다
(ex) char -> int, float -> double)
void foo(int);
void foo(double);
foo('a'); //char -> int로 숫자승격
foo(true); //bool -> int로 숫자승격
foo(1.5f); //float -> double로 숫자승격
4. 숫자 변환 (Numeric Conversion)
숫자 승격으로 해결되지 않는다면 숫자 변환을 시도한다
(ex) int -> double)
void foo(double);
void foo(std::string);
foo(1); //int -> double로 숫자 변환
5. 사용자 정의 변환
사용자 정의 타입에서 operator를 사용한 변환이 적용된다
class C
{
public:
operator int() { return 0; }
};
void foo(int);
void foo(double);
int main()
{
C c;
foo(c); //Class C는 int로 변환되고 호출된다
}
6. 가변 인수...
위의 모든 단계에서 함수를 찾을 수 없다면 가변 인수인 ...을 사용하는 함수가 호출된다
이 단계들을 거치고 함수를 못 찾는다면 컴파일 에러가 발생하게 된다
모호한 매칭 (Ambiguous Match)
컴파일러가 여러 후보의 함수를 찾았지만 어떤 함수가 더 명확한지 결정할 수 없을때 발생한다
void foo(int);
void foo(double);
foo(10L); //compile error, L은 int로, double로 둘다 변환이 가능하기 때문이다
foo(static_cast<int>(10L)); //이렇게 명확한 변환으로 해결이 가능
Function Delete
원치 않는 함수 호출을 방지하기 위해서는 = delete 키워드를 사용한다
함수가 존재하긴 하지만 호출 시 컴파일 에러를 발생시킨다
void foo(int) = delete;
void foo(double) = delete;
void foo(float);
void foo(1); //error
void foo(10.0); //error
void foo(10.f); //ok

delete는 함수가 존재하지 않는다는 의미가 아니다, 따라서 function overloading resolution에서 고려되며 선택될 시 컴파일 에러를 발생시킨다
만약 특정 타입 말고 전부 = delete를 시키고 싶으면 어떻게 하는게 좋을까? 하나하나 전부 = delete를 하는건 비효율적이다
이럴때 템플릿을 사용하면 편하다
void foo(int);
template <typename T>
void foo(T x) = delete;
//매개변수 타입이 int가 아닌 다른 타입의 foo()는 호출이 금지된다
함수 기본 인자
함수 기본 인자란 함수 매개변수에 이미 지정된 기본값을 의미한다, 함수 호출 시 해당 매개변수를 비워도 자동으로 default 값으로 넘어가게 된다
void foo(int a, int b = 10)
{
return a + b;
}
foo(100); //b에는 default값인 10이 넘어가 110이 나옴
기본 인자는 반드시 =로 지정해야 한다, ()나 {}로는 불가능하다
컴파일러가 컴파일 단계에서 기본 인자를 적용한다
foo(100)은 컴파일 단계에서 foo(100, 10)으로 변환된다는 의미이다
기존 함수에 새로운 매개변수를 추가할 경우 굉장히 유용하게 사용이 가능하다, 기존 코드와의 호환성 유지가 가능하면서 새로운 기능 추가가 가능하기 때문이다 (하지만 신중하게 사용해야 함)
여러개의 기본 인자도 가능하다, 단 맨 뒤에서 부터 기본인자가 있어야 한다 (중간에는 불가능)
void foo(int a, int b = 10, int c = 20); //ok
void foo(int a, int b = 10, int c); //x
또한 함수 선언부에서 기본값을 지정하고 정의부에서 다시 기본값을 지정할 수 없다
int foo(int a, int b = 20);
int main()
{
foo(10);
return 0;
}
int foo(int a, int b) //여기서 b = 20을 다시하면 컴파일 에러 발생
{
return a + b;
}
보통은 헤더에서 기본 인자를 지정하고 cpp에서는 다시 지정하지 않는다 (정의부에서는 주석만 달아서 값이 몇인지 표현한다)
함수의 기본 인자가 존재하면 타입이 구분된다 (즉 overloading이 가능하다)
하지만 기본 인수로 인해 모호한 매칭이 발생할 수 있다
void foo(int a = 10);
void foo(double d = 10.0);
foo(); //Ambiguous! compile error
