정신과 시간의 방
카테고리
작성일
2023. 2. 12. 23:16
작성자
risehyun

이번에 알아볼 것은 오버라이딩이다. 얼핏 보면 용어가 이전에 배웠던 오버로딩과 비슷해 보여 헷갈릴 수 있다.

하지만 두 용어는 전혀 별개의 개념을 가졌으므로 둘의 차이를 명확하게 이해하는 것이 중요하다.

면접에서도 둘의 차이점을 물어보는 것이 단골 주제이니 잘 알아두자.

 

오버로딩은 함수명은 똑같지만 인자 타입에서 차이가 생긴 함수들을 의미한다.

비록 이름은 같지만 대신 이 함수들은 인자가 타입이나 개수에서 다른 함수들과 차이가 있기 때문에 

컴파일러가 각각의 함수가 정확히 무엇을 지칭하는 것인지 구분 할 수 있다.

따라서 이 오버로딩이 있기 때문에 중복된 이름을 가진 함수가 한 프로그램에 여러 개 동시에 존재할 수 있다.

 

반대로 오버라이딩이란 상속 관계에서 발생하는 개념이다.

앞서 상속을 받은 자식 클래스는 부모 클래스에 있는 기능을 사용할 수 있다고 배웠다.

 

#include <iostream>
using namespace std;

class CParent
{
protected:
	int      m_i;

public:
	void SetInt(int _a)
	{
		m_i = _a;
	}

	void Output()
	{
		cout << "Parent" << endl;
	}

public:
	CParent()
		: m_i(0)
	{
		cout << "부모 생성자" << endl;
	}

	~CParent()
	{
		cout << "부모 소멸자" << endl;
	}
};

class CChild : public CParent
{
private:
	float m_f;

public:
	void SetFloat(float _f)
	{
		m_f = _f;
		m_i = 100;
	}

public:
	CChild()
		: m_f(0.f)
	{
		m_i = 0;
		cout << "자식 생성자" << endl;
	}
};

void FuncA()
{
	cout << "Function A" << endl;
}

void FuncB()
{
	FuncA();

	cout << "Function B" << endl;
}


int main()
{
	CParent parent;
	parent.Output(); // output은 CParent가 가진 함수이므로 이를 호출할 수 있다는 건 당연하다.

	CChild child;
	child.Output(); // CParent를 상속받은 CChild도 CParent에 구현된 함수를 호출 할 수 있다.

	

	return 0;
}

생성자, Output 함수, 소멸자가 차례대로 정상 실행된다.

 

그런데 만약 이 상황에서 CChild에서 상속받은 Output 함수의 출력 내용을 바꾸고 싶다면 어떻게 해야 할까?
가장 근본적으로 생각해보면 똑같은 이름의 함수를 CChild에서 생성하고 안의 출력 내용을 바꾸는 방법이 있을 수 있다.

이 경우 같은 이름의 함수가 하나는 부모 클래스에 있고, 다른 하나는 상속받은 자식 쪽에 있게 된다.

 

#include <iostream>
using namespace std;

class CParent
{
protected:
	int      m_i;

public:
	void SetInt(int _a)
	{
		m_i = _a;
	}

	void Output()
	{
		cout << "Parent" << endl;
	}

public:
	CParent()
		: m_i(0)
	{
		cout << "부모 생성자" << endl;
	}

	~CParent()
	{
		cout << "부모 소멸자" << endl;
	}
};

class CChild : public CParent
{
private:
	float m_f;

public:
	void SetFloat(float _f)
	{
		m_f = _f;
		m_i = 100;
	}

	void Output()
	{
		cout << "Child" << endl;
	}

public:
	CChild()
		: m_f(0.f)
	{
		m_i = 0;
		cout << "자식 생성자" << endl;
	}
};

void FuncA()
{
	cout << "Function A" << endl;
}

void FuncB()
{
	FuncA();

	cout << "Function B" << endl;
}


int main()
{
	CParent parent;
	parent.Output(); // output은 CParent가 가진 함수이므로 이를 호출할 수 있다는 건 당연하다.

	CChild child;
	child.Output(); // CParent를 상속받은 CChild도 CParent에 구현된 함수를 호출 할 수 있다.

	

	return 0;
}

 

이 상태로 프로그램을 실행하면 자식 쪽에 구현된 함수가 호출된다.

 

 

원래라면 함수명이 중복되고, 인자까지 같은 상황이기 때문에 링크 단계에서 오류가 발생해야 하지만

현재 두 클래스가 상속 관계에 있으므로 이것을 문제로 보지 않게 된다.

 

이처럼 자식 클래스가 자기가 상속받은 부모 쪽에 있는 기능을 덮어 써 재정의 하는 것을 오버라이딩이라고 한다.

 

 혹시 이미 자식 클래스에서 기능 하나를 오버라이딩 한 상태이지만 굳이 부모쪽이 가진 버전으로 해당 기능을 호출하고 싶다면 아래와 같이 함수를 지정 호출하면 된다.

 

int main()
{
	CParent parent;
	parent.Output(); // output은 CParent가 가진 함수이므로 이를 호출할 수 있다는 건 당연하다.

	CChild child;
	child.Output(); // CParent를 상속받은 CChild도 CParent에 구현된 함수를 호출 할 수 있다.

	child.CParent::Output(); // 자식 클래스에서 오버라이딩 한 함수를 다시 부모 버전으로 호출하고 싶을 때
	

	return 0;
}

하이라이트 표시 한 부분이 부모 버전으로 실행된 함수이다

오버라이딩을 왜 사용할까?
앞서 서술했듯이, 오버라이딩은 부모 쪽 기능을 가져다쓰면서 그 중 자식 클래스에서 사용해야 할 몇 가지 기능을 추가하거나 수정해서 사용하고 싶을 때 사용할 수 있다.

예를 들어, 몬스터를 구현하는 상황을 떠올려보자.
몬스터의 종류에는 근거리 공격을 하는 몬스터와 원거리 공격을 하는 몬스터가 있다.

두 몬스터는 근본적으로 걷고, 공격하고, 죽는다는 공통적인 기능을 가지고 있다.
이럴 때 몬스터란 하나의 근본 클래스를 만들어서 각각 근거리 몬스터와 원거리 몬스터에게 이것을 상속받아 공격하는 기능을 간편하게 추가할 수 있다.

하지만 둘은 공격하는 방식의 차이가 있으므로, 상속받은 공격 기능을 각각 오버라이딩하여 근거리 몬스터는 근거리에서 공격을 하고, 원거리 몬스터는 원거리에서 공격을 하도록 기능을 수정하면 걷고, 죽는다는 근본 기능을 공유하면서 공격이라는 기능을 자식 클래스의 특징에 맞게 세부 구현할 수 있다.
이처럼 적절한 상황에서 오버라이딩을 유용하게 사용할 수 있다. 

지금까지 배운 내용을 정리해보자.

<오버라이딩>
- 부모 클래스의 멤버 함수를 자식쪽에서 재정의 함으로써, 자식 클래스에 구현된 기능이 호출되도록 한다.

 

'C,C++' 카테고리의 다른 글

[C/C++] 공용체(union)  (0) 2023.05.03
다형성  (0) 2023.02.13
상속  (0) 2023.02.11
Tree 구현 + Enum  (0) 2023.02.10
[C++] list iterator  (0) 2023.02.06