C++ #07 다형성

underlier12·2020년 2월 5일
2

C++

목록 보기
7/19

07. 다형성

다형성의 기법

Polymorphism이란 여러 개의 서로 다른 객체가 동일한 기능을 서로 다른 방법으로 처리할 수 있는 기능을 의미한다. 예를 들어 칼, 대포, 총 등의 무기들은 공통적으로 '공격'이라는 동일한 기능을 다르게 수행할 수 있다.

image.png

따라서 무기 객체에서 attack() 함수를 실질적으로 구현할 필요없이 추상 클래스(Abstract Class)로 구현하면 효과적으로 설계를 할 수 있다.

image.png

오버라이딩 문제

자식 클래스에서 멤버 함수를 재정의하여 사용하는 것은 일반적으로 잘 동작하나 포인터 변수로 객체에 접근할 때 의도치 않게 동작할 수 있다. C++ 컴파일러는 포인터 변수가 가리키고 있는 변수의 타입을 기준으로 함수를 호출하지 않고 포인터 타입을 기준으로 함수를 호출한다.

일반 함수의 정적 바인딩

다음과 같은 경우 포인터 변수 p가 a, b를 각각 가리킨 후 show()함수를 호출했지만 포인터 변수로 접근했기에 포인터 변수의 타입인 A 타입을 기준으로 호출한다.

#include <iostream>
using namespace std;

class A
{
public:
	void show() { cout << "A 클래스입니다." << endl; }
};
class B: public A
{
	void show() { cout << "B 클래스입니다." << endl; }
};

int main(void)
{
	A* p;
	A a;
	B b;
	p = &a; p->show();
	p = &b; p->show(); // 여전히A 클래스의show() 함수를호출합니다.
	system("pause");
}

결과는 둘 다 "A 클래스 입니다."가 출력된다

가상 함수

Virtual Function이란 자식 클래스에서 재정의 할 수 있는 멤버 함수이다. virtual 키워드를 이용해 가상 함수를 선언할 수 있으며 자식 클래스에서 가상 함수를 재정의하면 재정의된 멤버 함수 또한 가상 함수로 분류된다.

가상 함수 테이블

C++ 컴파일러는 가상 함수 테이블(Virtual Function Table)을 이용해 가상 함수를 다루게 된다. C++ 컴파일러는 각각의 객체마다 가상 함수 테이블을 가리키는 포인터를 저장하기 위한 멤버를 하나씩 저장한다.

이 테이블에는 특정 클래스의 객체들을 위해 선언된 가상 함수들의 주소가 저장되며 가상 함수를 호출 할 때 C++ 프로그램은 가상 함수 테이블에 접근하여 자신이 필요한 함수의 주소를 찾아 호출한다.

동적 바인딩

C++은 특정한 함수를 호출할 때 해당 함수의 루틴이 기록된 메모리 주소를 알아야 한다. 특정한 함수를 호출하는 소스코드에서 실제로 함수가 정의된 메모리 공간을 찾기 위해서 바인딩(Binding) 과정이 필요하다.

일반적으로 함수의 호출은 컴파일 시기에 고정된 메모리 주소를 이용한다. 이러한 방식을 정적 바인딩(Static Binding)이라 하는데 C++의 일반적인 멤버 함수는 이런 방식을 사용한다.

다만 가상함수는 프로그램이 실행 될 때 객체를 결정한다는 점에서 컴파일 시간에 객체를 특정할 수 없다. 그래서 가상 함수는 실행 시간 때 올바른 함수가 실행 될 수 있도록 동적 바인딩(Dynamic Binding)을 사용한다.

가상함수 : 동적 바인딩

다음과 같이 가상 함수로 선언해주면 포인터 변수 p가 순차적으로 a, b 객체를 가리키더라도 show()함수 호출 시 각기 달리 호출 되는 것을 알 수 있다.

#include <iostream>
using namespace std;

class A
{
public:
	virtual void show() { cout << "A 클래스입니다." << endl; }
};
class B: public A
{
	virtual void show() { cout << "B 클래스입니다." << endl; }
};

int main(void)
{
	A* p;
	A a;
	B b;
	p = &a; p->show();
	p = &b; p->show(); // 여전히A 클래스의show() 함수를호출합니다.
	system("pause");
}

결과는 "A 클래스입니다.", "B 클래스입니다."가 순차적으로 출력

가상 클래스의 소멸자

C++에서 상속 관계가 있으며 메모리 해제를 해야 하는 경우 반드시 부모 클래스의 소멸자를 가상 함수로 선언해야 한다. 만약 그렇지 않으면 자식 클래스의 소멸자는 호출되지 않고 부모 클래스의 소멸자만 호출되어 자식 클래스의 객체가 해제되지 않는 현상이 발생한다.

순수 가상 함수

C++의 가상 함수는 기본적으로 반드시 재정의할 필요는 없다. 하지만 순수 가상 함수(Pure Virtual Function)은 자식 클래스에서 반드시 재정의를 해야 한다. 그래서 일반적으로 순수 가상 함수는 부모 클래스에서 함수 동작을 정의하지 않는 대신 자식 클래스에서 반드시 정의해야 사용 가능하다.

#include <iostream>
using namespace std;

class A
{
public:
	virtual void show()=0 { }
};
class B: public A
{
	virtual void show() { cout << "B 클래스입니다." << endl; }
};

int main(void)
{
	A* p;
	B b;
	//p = &a; p->show();
	p = &b; p->show(); // 여전히A 클래스의show() 함수를호출합니다.
	system("pause");
}

추상 클래스

추상 클래스(Abstract Class)란 하나 이상의 순수 가상 함수를 포함하는 클래스를 의미한다. 추상 클래스를 활용하면 다형성을 효과적으로 프로그램 상에서 구현할 수 있다.

profile
logos and alogos

0개의 댓글