개발/C, C++

함수 오버로딩과 오버라이딩

토레로르 2023. 11. 30. 11:05

함수 오버로딩(Function Overloading)

오버로딩은 C++의 다형성을 활용하는 기법이며, 함수 중복 정의를 의미한다. 조금 풀어서 쓰자면, 동일한 이름의 함수를 여러 번 정의하여 매개변수의 개수나 타입에 따라 다르게 동작하도록 하는 기능을 말한다.

 

오버로딩 된 함수 호출 시, 어떤 함수를 호출할 것인지 결정하는 것은 함수 시그니처 (Function Signature)이다. 함수 시그니처는 함수의 원형에 명시되는 매개변수 리스트이다. 즉 함수 시그니처는 함수의 이름과 매개변수의 타입, 개수, 순서로 결정된다.

 

* 함수 시그니처 예시

void Funtion(int x, float y) { ... }
// 위의 함수 시그니처는 다음과 같다.
// 이름 : Funtion
// 매개변수 타입 및 순서 : int, float

 

** 함수 시그니처에 유의할 점 : 함수 시그니처에 반환 타입은 포함되지 않는다.

 

 

다시 한번 풀어서 쓰자면 오버로딩을 다음과 같이 정의할 수 있다. 서로 다른 시그니처를 갖는 여러 함수를 같은 이름으로 정의하는 것이다.

 

그렇다면 오버로딩을 왜 쓰는가?

 

오버로딩이 허용되지 않는다고 가정을 한 상황에서 A와 B를 더하는 함수 Plus가 있다고 가정해 보자. 함수 Plus는 int와 int를 더한 값을 출력하는 함수이다.

void Plus(int a, int b){
	std::cout << a + b;
}

 

그런데 사용자가 float와 float를 더해서 float의 결과를 출력해야 하는 경우가 생길 수도 있다. 그럴 때, 위의 Plus를 사용할 수도 있지만, 그 경우 원하는 결괏값을 정확히 얻지는 못할 것이다. 

void Plus_float(float a, float b){
	std::cout << a + b;
}

 

때문에 비슷한 동작을 하는 새 함수를 위와 같이 만들었다. 오버로딩이 허용되지 않았기 때문에, 함수의 명칭은 Plus_float로 작성되었다. 그렇다면 Double과 Double을 더하는 경우는 어떨까? long과 long을 더하는 경우가 필요할 수도 있다. 어쩌면 int와 float를 더해야 하는 경우도....

 

그때마다 Plus함수의 뒤에 _intfloat니 _double이니 하는 값들을 무한히 붙여가며 함수를 만들 수도 있지만, 그 모든 상황을 가정해 가며 함수의 이름을 붙이는 것도 지난한 일이다.

 

오버로딩을 사용하는 주요한 이유는 바로 위와 같은 예시를 방지하고, 코드의 가독성과 재사용성을 향상하기 위함이다.

 

 

*** 연산자 오버로딩이란?

함수 오버로딩과 연산자 오버로딩은 둘 다 C++에서 다형성을 활용하는 기법이지만, 다른 측면에서 사용된다. 

 

연산자 오버로딩은 사용자가 정의한 클래스나 구조체에 대해 기본 연산자를 다르게 정의하는 것이다. 대표적인 예시로 std::string 클래스의 +연산자가 연산자 오버로딩이 적용된 것이라 할 수 있다.

 

 

 

오버라이딩(Function Overriding)

오버라이딩은 함수 재정의를 의미한다. 오버라이딩이란 어떤 함수를 실행할 때, 부모의 함수를 사용하지 않고 자식 클래스에 같은 이름, 매개변수(시그니처가 같은)의 함수를 재정의하여 사용하는 것이다. 때문에 오버라이딩을 사용하려면 상속이 선제되어야 한다. 

 

오버라이딩은 다음과 같은 규칙에 따라 이루어진다.

 

1. 오버라이딩되는 함수는 부모 클래스와 자식 클래스에서 동일한 함수 시그니처(이름, 매개변수 리스트)에 더해 동일한 반환 타입을 가져야 한다.

 

* 반환 타입이 다른 경우에는 다른 함수를 만드는 것과 같다.

 

2. 오버라이딩된 함수는 부모 클래스에서 가상 함수(virtual)로 선언되어야 한다.

class Base {
public:
	virtual void Function(int x){
    	std::cout << x << "\n";
    }
}

class Child : public Base {
public:
	virtual void Function(int x) override {
    	std::cout << "child " << x << "\n";
    }
}

 

위 예시에서 부모 클래스 Base는 Function 함수를 virtual 키워드를 사용함으로 가상 함수로 선언했다. 자식 클래스 Child는 Function 함수가 오버라이딩 될 수 있도록 같은 함수 시그니처와 반환 타입으로 작성했고, 내부 동작을 재정의 했다.

 

 

오버로딩을 사용하는 이유는?

오버라이딩을 사용하는 주된 이유는 다형성을 구현하여 코드의 유연성을 높이고, 객체 지향 프로그래밍의 특징을 활용하기 위함이다. 

 

* 다형성의 구현 예시

#include <iostream>

class Animal{
public:
	virtual void Bark() const{
    	std::cout << "Animal is Bark\n";
    }
};

class Dog : public Animal{
public:
	virtual void Bark() const{
    	std::cout << "멍멍\n";
    }
};

class Lion : public Animal{
public:
	virtual void Bark() const{
    	std::cout << "어흥\n";
    }
};

int main(){
	Animal* dog = new Dog();
	Animal* lion = new Lion();
    
    dog->Bark(); // 멍멍
    lion->Bark(); // 어흥
    
    delete dog;
    delete lion;
    
    return 0;
}