부모와 자식 사이의 다형성에 관해 알아보자. 부모와 자식에 같은 이름의 함수가 오버라이딩 된 상태에서, 어떤 타입으로 객체를 생성하는지에 따라 객체가 어떤 클래스의 멤버 함수를 호출하는지 알아보자.
여기서 말하는 타입과 객체는 다음을 말한다.
// Warrior 클래스형 포인터 타입으로 Warrior 객체 생성
Warrior *a = new Warrior();
// Character 클래스형 포인터 타입으로 Warrior 객체 생성
Character *b = new Warrior();
Charcter 클래스와 Warrior 클래스는 부모와 자식 관계이며, 같은 이름의 멤버 함수가 있다.
class Character {
public:
void sayHello(std::string const &target);
};
class Warrior : public Character {
public:
void sayHello(std::string const &target);
};
그렇다면 a
와 b
객체에서 sayHello
함수를 실행한다면, 타입 기반으로 실행이 될까, 생성한 객체 기반으로 실행이 될까?
void Character::sayHello(std::string const &target) {
std::cout << "Hello " << target << " !" << std::endl;
}
void Warrior::sayHello(std::string const &target) {
std::cout << "F*** off" << target << ", I don't like you !" << std::endl;
}
int main(void) {
Warrior *a = new Warrior();
Character *b = new Warrior();
a->sayHello("students");
b->sayHello("students");
}
결과를 보면, 타입 기반으로 함수가 호출되는 걸 볼 수 있다. a
는 Warrior
타입이었기 때문에 F*** off
로 시작하는 문장이 출력됐고, b
는 Character
타입이었기 때문에 Hello
로 시작하는 문장이 출력되는 것을 확인할 수 있다.
이렇게 되는 이유는 컴파일 시 코드를 정적 바인딩되기 때문이다. 그렇기 때문에 어떤 클래스 기반으로 객체를 생성하든 타입에 맞는 멤버 함수가 호출한다.
그렇다면 어떻게 해야 타입이 아닌 클래스에 따라 함수를 호출할 수 있을까? 이때 virtual
을 사용한다.
class Character {
public:
virtual void sayHello(std::string const &target);
};
class Warrior : public Character {
public:
virtual void sayHello(std::string const &target);
};
virtual
라는 예약어를 사용하면 해당 멤버 함수를 재정의할 것을 기대하고 정의하는 함수가 된다. 이를 가상함수라고 하며 파생 클래스에서 새로운 내용으로 재정의할 수 있다. 또한 이전에는 정적 바인딩을 했다면 포인터가 가리키는 객체에 따라 멤버 함수를 선택하는 동적 바인딩을 하게 된다.
#include <string>
#include <iostream>
class Character {
public:
virtual void sayHello(std::string const &target);
};
class Warrior : public Character {
public:
virtual void sayHello(std::string const &target);
};
class Cat
{
//[...]
};
void Character::sayHello(std::string const &target) {
std::cout << "Hello " << target << " !" << std::endl;
}
void Warrior::sayHello(std::string const &target)
{
std::cout << "F*** off" << target << ", I don't like you !" << std::endl;
}
int main(void) {
// 워리어는 워리어 클래스를 생성하기 때문에 작동한다.
Warrior *a = new Warrior();
// 캐릭터는 워리어클래스의 부모이기 때문에 작동한다.
Character *b = new Warrior();
// 캐릭터는 워리어 클래스의 상속을 받지 않기 떄문에 작동안한다.
// Warrior *c = new Character();
// 캐릭터는 고양이 클래스의 상속을 받지 않기 때문에 작도 안한다.
// Character *d = new Cat();
a->sayHello("students");
b->sayHello("students");
}