본문 바로가기
개발/C, C++

C++ - virtual 키워드

by 피로물든딸기 2021. 8. 14.
반응형

C, C++ 전체 링크
 
아래의 클래스를 보자.
SNACK과 MILK 클래스는 FOOD 클래스를 상속한다.

#include <stdio.h>
#include <iostream>

using namespace std;

class FOOD
{
private:
	int price = 0;
public:
	void printName() { cout << "FOOD Class" << endl; }
	void printPrice() { cout << this->price << endl; }
	void printLine() { cout << "=================" << endl; }
};

class SNACK : public FOOD
{
private:
	int price = 1000;
public:
	void printName() { cout << "SNACK Class" << endl; }
	void printPrice() { cout << this->price << endl; }
};

class MILK : public FOOD
{
private:
	int price = 500;
public:
	void printName() { cout << "MILK Class" << endl; }
	void printPrice() { cout << this->price << endl; }
};

 
main문을 아래와 같이 작성해보자.

int main(void)
{
	FOOD food;
	SNACK snack;
	MILK milk;

	food.printName(); food.printPrice(); food.printLine();
	snack.printName(); snack.printPrice(); snack.printLine();
	milk.printName(); milk.printPrice(); milk.printLine();

	return 0;
}

 
그러면 아래의 출력 결과를 볼 수 있다.
FOOD의 printName과 printPrice를 snack과 milk가 상속하여, 각각의 클래스에 맞게 출력하고 있다.
sanck과 milk는 printLine을 오버라이딩하지 않았기 때문에 food의 printLine을 그대로 출력한다.

 
또는 아래와 같이 작성해도 같은 결과를 얻는다.

int main(void)
{
	FOOD* food = new FOOD();
	SNACK* snack = new SNACK();
	MILK* milk = new MILK();

	food->printName(); food->printPrice(); food->printLine();
	snack->printName(); snack->printPrice(); snack->printLine();
	milk->printName(); milk->printPrice(); milk->printLine();

	return 0;
}

이제 상속을 응용해보자.
SNACK과 MILK를 여러 개 담을 배열을 선언하고 싶다.
SNACK과 MILK를 따로따로 담는 것보다 하나의 FOOD로 관리하고 싶다고 하자.

int main(void)
{
	FOOD* foodList[3];

	foodList[0] = new SNACK();
	foodList[1] = new MILK();
	foodList[2] = new SNACK();

	for (int i = 0; i < 3; i++)
	{
		foodList[i]->printName();
		foodList[i]->printPrice();
		foodList[i]->printLine();
	}

	return 0;
}

 
출력결과는 예상과 다르다.

 
foodList의 원래 형태는 FOOD class지만, 실제 객체는 SNACK이나 MILK가 된다.
그러나 FOOD의 printName과 printPrice가 실행되었다.
 
즉, 형변환이 이루어진 포인터 변수원래 형태(부모)의 함수를 호출하게 된다.
 
virtual을 이용하면 형변환이 이루어지더라도 자식 클래스의 함수(오버라이딩)를 호출할 수 있게 된다.
→ foodList는 run time에 함수를 유연하게(다형성, Polymorphism) 교체(동적 바인딩, dynamic binding)한다.
 
아래와 같이 오버라이딩이 필요한 함수에 virtual 키워드를 붙여주면 된다.

class FOOD
{
private:
	int price = 0;
public:
	virtual void printName() { cout << "FOOD Class" << endl; }
	virtual void printPrice() { cout << this->price << endl; }
	void printLine() { cout << "=================" << endl; }
};

 
포인터 뿐만 아니라 참조 변수인 경우에도 가능하다.

int main(void)
{
	SNACK snack;
	MILK milk;

	FOOD& fd1 = snack;
	FOOD& fd2 = milk;

	fd1.printName();
	fd1.printPrice();
	fd1.printLine();

	fd2.printName();
	fd2.printPrice();
	fd2.printLine();

	return 0;
}

 
이제 foodList는 FOOD를 상속한 class에 대해 유연한 대응이 가능해졌다.
 
참고로, virtual은 형변환이 이루어진 포인터 변수, 참조 변수인 경우만 가능하다.
main 문을 아래와 같이 변경하면 virtual을 선언하더라도 원하는 결과를 얻을 수 없다.
가상 함수가 run time에 다형성을 얻기 위해서 포인터, 참조를 통해 접근하기 때문이다.

int main(void)
{
	FOOD foodList[3];

	foodList[0] = SNACK();
	foodList[1] = MILK();
	foodList[2] = SNACK();

	for (int i = 0; i < 3; i++)
	{
		foodList[i].printName();
		foodList[i].printPrice();
		foodList[i].printLine();
	}

	return 0;
}


- virtual 키워드는 자식 클래스에 생략할 수 있다.
 
- 추후 코드를 설계할 때, 특정 가상 함수가 재정의 되는 것을 막고 싶다면 final 키워드를 이용하면 된다.
아래의 경우 SNACK에서 printName을 재정의하려고 하면 compile error가 발생하는 것을 알 수 있다.


Java의 경우 virtual 키워드가 따로 없다.
Java는 선언된 타입에 상관 없이, 생성된 객체를 기준으로 함수를 바로 호출하기 때문이다.

public class FOOD {
	private int price = 0;
	
	public void printName() { System.out.println("FOOD Class"); }
	public void printPrice() { System.out.println(price); }
	public void printLine() { System.out.println("================="); }
}

 

public class SNACK extends FOOD {
	private int price = 1000;
	
	public void printName() { System.out.println("SNACK Class"); }
	public void printPrice() { System.out.println(price); }
}

 

public class MILK extends FOOD {
	private int price = 500;
	
	public void printName() { System.out.println("MILK Class"); }
	public void printPrice() { System.out.println(price); }
}

 

public class Main {
	public static void main(String[] args) {

		FOOD food = new FOOD();
		SNACK snack = new SNACK();
		MILK milk = new MILK();
		
		food.printName(); food.printPrice(); food.printLine();
		snack.printName(); snack.printPrice(); snack.printLine();
		milk.printName(); milk.printPrice(); milk.printLine();
		
		System.out.println("food에 snack을 대입");
		food = snack;
		food.printName(); food.printPrice(); food.printLine();
		
		System.out.println("FOOD 타입에 MILK를 생성");
		FOOD milkFood = new MILK();
		milkFood.printName(); milkFood.printPrice(); milkFood.printLine();
	}
}

 

FOOD Class
0
=================
SNACK Class
1000
=================
MILK Class
500
=================
food에 snack을 대입
SNACK Class
1000
=================
FOOD 타입에 MILK를 생성
MILK Class
500
=================
반응형

댓글