Polymorphism(Virutal)

다형성(Polymorphism)
여러 개의 서로 다른 객체가 동일한 기능을 서로 다른 방법으로 처리할 수 있는 기능을 의미한다.
예를 들어 게임 프로그램에서 칼, 대포, 총 등의 무기에는 공통적으로 ‘공격’이라는 동일한 기능을 수행
자식 클래스에서 오버라이딩의 문제점
자식 클래스에서 멤버 함수를 재정의하여 사용하는 것은 일반적으로 정상적으로 동작한다.
하지만 포인터 변수로 객체에 접근할 때는 예상치 못한 결과 발생 가능
C++ 컴파일러는 포인터 변수가 가리키고 있는 변수 타입을 기준으로 함수를 호출하지 않고,
포인터의 타입을 기준으로 함수를 호출한다.

따라서 A라는 객체를 가리키는 포인터 변수는 A 객체의 멤버 함수만을 호출할 수 있다.
#include <iostream>
#include <string>
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();
system("pause");
return 0;
}

A 클래스입니다.

A 클래스입니다.

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

C++ 컴파일러는 가상 함수 테이블(Virtual FUnction Table)을 이용해 가상 함수를 다루게 된다.
C++ 컴파일러는 각각의 객체마다 가상 함수 테이블을 가리키는 포인터를 저장하기 위한 멤버를 하나씩 저장
가상 함수 테이블에는 특정한 클래스의 객체들을 위해 선언된 가상 함수들의 주소가 저장
따라서 가상 함수를 호출하면 C++프로그램은 가상함수 테이블에 접근하여 자신이 필요한 함수의 주소를 찾아 호출

위의 과정은 동적 바인딩을 통해 이루어지며 컴퓨팅 리소스를 소모한다.
따라서 C++은 기본적으로 정적 바인딩을 채택한다.

자식 클래스가 재정의할 가능성이 있는 멤버 함수들은 가상 함수로 선언하는 것이 좋다.
가상 클래스의 소멸자
C++에서 상속 관계가 있으면서 그와 동시에 메모리 해제를 해야하는 경우에는
반드시 부모 클래스의 소멸자를 가상함수로 선언해야 한다.
(부모 포인터로 객체를 삭제하면 부모 클래스의 소멸자가 호출됨)

만일 다형성을 이용할 때, 소멸자를 가상 함수로 선언하지 않으면, 자식 클래스의 소멸자는 호출되지 않고,
부모 클래스의 소멸자만 호출되기 때문에 자식 클래스의 객체는 여전히 정상적으로 해제되지 않음
동적 바인딩
C++은 특정한 함수를 호출할 때, 해당 함수의 루틴이 기록된 메모리 주소를 알아야 한다.
특정한 함수를 호출하는 소스코드에서 실제로 함수가 정의된 메모리 공간을 찾기 위해서는 바인딩(Binding) 과정이 필요

일반적으로 함수의 호출은 컴파일 시기에 고정된 메모리 주소를 이용-정적 바인딩(static binding)
(C++의 일반적인 멤버 함수는 모두 이러한 정적 바인딩 사용)

다만 가상함수는 프로그램이 실행될 때 객체를 결정하기 때문에 컴파일 시간에 객체를 특정할 필요가 없다.
가상함수는 실행시간 때 올바른 함수가 실행될 수 있도록 동적 바인딩(Dynamic Binding) 사용
#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클래스입니다.

B클래스입니다.

순수 가상 함수
C++에서 가상함수는 반드시 재정의할 필요는 없다. 하지만 순수 가상 함수(Pure Virtual Function)는
자식 클래스에서 반드시 재정의 해주어야 한다.

일반적으로 부모클래스에서 함수 동작의 본체를 정의하지 않음.
자식 클래스에서 반드시 이를 재정의해야 사용할 수 있다.

순수 가상 함수는 ‘=0’키워드를 붙여서 선언
#include <iostream>
using namespace std;
class A{
public:
virtual void show()=0;
};
class B{
public:
virtual void show(){
cout<<"B."<<endl;
}
};
추상 클래스(Abstract Class)
한 개이상의 순수 가상 함수를 포함하는 클래스

자식 클래스는 추상 클래스를 상속 받은 이후에 반드시 순수 가상 함수를 모두 오버라이딩 해야 비로소 해당 객체를 사용 가능
class A abstract{
// abstract .( )
public:
virtual void func()=0;
}
c++은  특별히 interface를 위한 키워드는 존재하지 않는다.
자바와 달리 c++은 다중 상속을 허용하기 때문에 추상 클래스를 통해 Interface 역할을 수행 가능