Advanced C++

[C++ 클래스] 가상 함수

로파이 2021. 1. 29. 19:30

가상 함수 Virtual Function

가상 함수란 C++ 객체 지향 프로그래밍에서 상속으로 클래스의 특정 메서드가 재정의될 수 있음을 암시할 때 사용한다. 

클래스의 멤버 함수 앞에 virtual 키워드를 붙임으로 해당 메서드는 가상 함수로 명할 수 있다.

 

다음 ClassA를 상속하는 ClassB를 정의하고 print_name() 메서드를 virtual 키워드와 함께 정의하여 파생클래스에서 재정의함을 암시한다.

ClassB는 ClassA의 print_name() 메서드를 재정의 (오버라이드) 하여 해당 기능을 ClassB에 맞게 재구현하는 것이다.

class ClassA
{
public:
	const char* _name;
public:
	ClassA(const char* name) : _name(name) {}
	void print_func()
	{
		cout << "A's default method" << endl;
	}
	virtual void print_name() {
		cout << "A's method" << endl;
		cout << _name << endl;
	}
};

class ClassB : public ClassA
{
public:
	ClassB(const char* name) : ClassA(name) {}
	void print_func()
	{
		cout << "B's default method" << endl;
	}
	virtual void print_name()
	{
		cout << "B's method" << endl;
		cout << _name << endl;
	}
};
  • 가상 함수의 용도는 해당 함수가 호출될 때 실제 객체의 클래스에 해당하는 함수를 호출하도록 한다.
  • 따라서 virtual 키워드로 정의되지 않은, 가상 함수가 아닌 일반 메서드는 호출될 때 참조하고 있는 변수의 타입에 해당하는 함수를 호출하게 된다.

다음 예시에서 결과를 확인할 수 있다.

int main()
{
	ClassA *ptr = new ClassB("jin");
	
	ptr->print_func(); // A's method
	ptr->print_name(); // B's method

	return 0;
}

추상 클래스 - 가상 함수의 용도

 

가상 함수는 프로그래머가 클래스를 설계할 때 해당 클래스를 상속하는 클래스의 용도를 미리 생각하는 것이다.

다음과 같이 가상 함수 끝에 "= 0;"이라는 표현을 추가하여 이 "순수 가상 함수"화 할 수 있다.

  • 순수 가상 함수를 하나라도 포함하는 클래스를 추상 클래스 Abstract Class 라고 한다.
  • 순수 가상 함수는 반드시 파생 클래스에서 구현을 해야하며 구현 내용이 없을 시 컴파일 에러가 발생한다.
  • 또한 순수 가상 함수가 구현되있지 않기 때문에 호출이 불가능하고 추상 클래스의 인스턴스를 생성할 수 없다.
class Shape
{
public:
	Point[] _points;
	Shape(Point[] points);
	virtual void Draw() = 0;
};
  • Shape 클래스를 설계할 때 Shape 클래스를 상속하여 다양한 도형을 정의하고 그에 맞는 그리기 기법을 달리 구현하기 위해 순수 가상 함수가 포함된 추상 클래스로 정의한다.
  • 추상 클래스는 인터페이스의 역할로 사용자가 Shape를 상속하는 다양한 객체를 조작하기 위한 공통 기능을 제공 한다.

가상 소멸자

가상 함수의 이용은 소멸자에도 적용이 되는데, 이는 부모 클래스에 동적 할당을 사용한 멤버 변수가 있을 때 꼭 필요하다.

class ImplicitData
{
	char* data;
public:
	ImplicitData() { data = new char; }
	~ImplicitData() { delete data; cout << "ImplicitData Destructor is called" << endl; }
};
class ExplicitData : public ImplicitData
{
	int* id;
public:
	ExplicitData() { id = new int; }
	~ExplicitData() { delete id; cout << "ExplicitData Destructor is called" << endl;  }
};

ImplicitData 클래스는 char형 변수에 메모리를 동적 할당하고 있고 ExplicitData 클래스는 int형 변수에 메모리를 동적 할당하고 있다.

ExplicitData 인스턴스가 소멸될 시 두 변수에 대해 올바르게 메모리 해제가 되어야하는데,

"가상 함수화 되지 않은" 함수의 호출은 참조자의 타입을 따르기 때문에 참조자 타입이 부모 클래스일 경우 자식 클래스의 소멸자는 호출되지 않는다. 

 

보통 사용자는 추상 클래스 타입의 포인터로 파생 클래스 타입의 인스턴스를 사용하므로 이러한 일은 자주 일어날 수 있다.

ex) Shape* aShape = new Rectangle();

     aShape->draw();

 

다음 예시 코드에서 메모리 해제가 되지 않은 현상을 볼 수 있다.

int main()
{
	ImplicitData *data = new ExplicitData;
	// ExplicitData destructor is not called !
	delete data;
	return 0;
}

따라서 소멸자를 가상 함수로 정의하여 참조자의 타입이 아니라 참조하고 있는 인스턴스의 타입의 소멸자를 호출하게 한다.

 

자식 소멸자를 호출하면 자동으로 상속한 자식 부터 부모 클래스의 소멸자까지 차례대로 호출한다.

※ 생성자의 경우 부모 생성자부터 자식 생성자까지 호출

class ImplicitData
{
	char* data;
public:
	ImplicitData() { data = new char; }
	virtual ~ImplicitData() { delete data; cout << "ImplicitData Destructor is called" << endl; }
};
class ExplicitData : public ImplicitData
{
	int* id;
public:
	ExplicitData() { id = new int; }
	virtual ~ExplicitData() { delete id; cout << "ExplicitData Destructor is called" << endl;  }
};
int main()
{
	ImplicitData *data = new ExplicitData;
	delete data;
	return 0;
}

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

[C++] 예외 처리 Exception  (0) 2021.02.20
[C++ 클래스] 순수 가상 소멸자  (0) 2021.02.18
[C++ 클래스] 가상 함수 테이블  (0) 2021.01.29
[C++] 이동 시맨틱과 r-value 참조형  (0) 2021.01.28
[C++] l-value와 r-value  (0) 2021.01.28