본문 바로가기
C++

[C++] 일반 함수와 가상 함수의 차이점 & 가상 소멸자 사용 이유

728x90
반응형

가상함수란?

    - 함수의 다형성을 구현하기 위해 상속받은 클래스의 함수를 재정의 하는 함수

 

일반함수와 다른점은? 

    - 일반 함수로도 상속받은 클래스에 함수 재정의가 가능합니다. 다만, 일반 함수의 경우 컴파일할때 결정 되지만, 가상함수는 런타임에 결정이 됩니다. 

 

 

#include <iostream>
using namespace std;

class Parent
{
public:
	void NormalFunc() { cout << "Parent's NormalFunc" << endl; };
	virtual void VirtualFunc() { cout << "Parent's VirtualFunc" << endl; };
};

class Child : public Parent
{
public:
	void NormalFunc() { cout << "Child's NormalFunc" << endl; };
	void VirtualFunc() override { cout << "Child's VirtualFunc" << endl; };
};

int main()
{
	Parent* parent = new Parent();
	Child* child = new Child();

	parent->NormalFunc();
	parent->VirtualFunc();

	child->NormalFunc();
	child->VirtualFunc();

	return 0;
}

위에 보이시는바와 같이 각자의 객체로 생성하여 각각의 함수를 불러낼경우 일반함수와 가상함수의 차이가 없습니다. 즉, 캐스팅(형변환)을 하여 런타임도중에 클래스 형식이 바뀌지 않으면 차이가 없습니다.

 

 

 

#include <iostream>
using namespace std;

class Parent
{
public:
	void NormalFunc() { cout << "Parent's NormalFunc" << endl; };
	virtual void VirtualFunc() { cout << "Parent's VirtualFunc" << endl; };
};

class Child : public Parent
{
public:
	void NormalFunc() { cout << "Child's NormalFunc" << endl; };
	void VirtualFunc() override { cout << "Child's VirtualFunc" << endl; };
};

int main()
{
	Parent* parent;
	Child* child = new Child();

	parent = dynamic_cast<Parent*>(child);
	parent->NormalFunc();
	parent->VirtualFunc();

	return 0;
}

이번에는 Child함수를 Parent함수로 업캐스팅후의 실행하였습니다. 앞선 결과와는 달리 하나의 객체에서 두 가지 클래스의 함수가 실행되었습니다.

 

그 이유는 일반 함수는 컴파일타임에 parent객체에서 실행하는 함수는 Parent class의 함수를 실행하라고 결정하였기 때문에 Parent함수가 실행되었습니다.

 

반면에 가상함수는 런타임 도중의 결정되기 때문에 객체가 Parent class형식일 지라도 Child로 생성이 되었기 때문에 Child class의 함수를 실행한 것입니다.

 

 

가상 소멸자란?

    - 위와 같이 업캐스팅후에 할당해제를 할경우 실제 객체는 Child class임에도 Parent Class의 소멸자가 호출이 되어 제대로된 할당해제가 안될수도 있습니다. 이는, 소멸자도 일반함수처럼 컴파일타임에 결정되기 때문인데 이러한 문제점을 해결하기위해 가상 소멸자가 존재합니다. 가상 소멸자를 실행할 경우 해당 객체의 실제 타입(Child class)의 소멸자 실행후 상위 클래스(Parent class)의 소멸자를 실행합니다.

#include <iostream>
using namespace std;

class Parent
{
public:
	void NormalFunc() { cout << "Parent's NormalFunc" << endl; };
	virtual void VirtualFunc() { cout << "Parent's VirtualFunc" << endl; };
	~Parent() { cout << "delete Parent" << endl; };
};

class Child : public Parent
{
public:
	int* ptr = nullptr;
	void NormalFunc() { cout << "Child's NormalFunc" << endl; };
	void VirtualFunc() override { cout << "Child's VirtualFunc" << endl; };
	~Child() { cout << "delete Child" << endl; delete ptr; };
};

int main()
{
	Parent* parent;
	Child* child = new Child();
	child->ptr = new int();

	parent = dynamic_cast<Parent*>(child);
	delete parent;

	return 0;
}

일반 소멸자로 소멸시킬 경우 Parent class의 소멸자만 실행되기 때문에 Child class에서 동적할당 받은 멤버가 제대로 할당해제 되지 않습니다.

 

 

 

#include <iostream>
using namespace std;

class Parent
{
public:
	void NormalFunc() { cout << "Parent's NormalFunc" << endl; };
	virtual void VirtualFunc() { cout << "Parent's VirtualFunc" << endl; };
	virtual ~Parent() { cout << "delete Parent" << endl; };
};

class Child : public Parent
{
public:
	int* ptr = nullptr;
	void NormalFunc() { cout << "Child's NormalFunc" << endl; };
	void VirtualFunc() override { cout << "Child's VirtualFunc" << endl; };
	~Child() override { cout << "delete Child" << endl; delete ptr; };
};

int main()
{
	Parent* parent;
	Child* child = new Child();
	child->ptr = new int();

	parent = dynamic_cast<Parent*>(child);
	delete parent;

	return 0;
}

소멸자를 가상 소멸자로 바꿔준경우 Child class의 소멸자 또한 실행되기 때문에 Child class에서 동적할당 받은 메모리까지 정상적으로 할당 해제 됩니다.

반응형