Virtual Function

가상함수(Virtual Function)는 기본 클래스에는 선언되어 있고, 기반클래스에는 재 정의되는 함수를 의미한다. 기반클래스와 기본클래스가 할당되어있고, 기본클래스에서 가상함수를 사용하는 입장에서 기반클래스의 인스턴스를 참조하게 된다면 run-time시간에 기반클래스의 재 정의된 가상함수를 가리켜 실행하게된다. 즉 자료형에 기반하지 않고 virtual pointer가 실제로 가리키는 객체를 참조하여 호출 대상을 결정

// CPP program to illustrate 
// concept of Virtual Functions 

#include <iostream> 
using namespace std; 

class base { 
public: 
	virtual void print() 
	{ 
		cout << "print base class" << endl; 
	} 

	void show() 
	{ 
		cout << "show base class" << endl; 
	} 
}; 

class derived : public base { 
public: 
	void print() 
	{ 
		cout << "print derived class" << endl; 
	} 

	void show() 
	{ 
		cout << "show derived class" << endl; 
	} 
}; 

int main() 
{ 
	base* bptr; 
	derived d; 
	bptr = &d; 

	// virtual function, binded at runtime 
	bptr->print(); // print derived class

	// Non-virtual function, binded at compile time 
	bptr->show(); // print show base class
} 

가상함수를 만든 클래스의 객체가 생성되면 가상함수 테이블을 가리키는 virtual pointer가 객체안에 삽입이 된다. 그리고 run-time시간에 binding된 클래스의 타입을 고려하여 가상함수 테이블에서 타입에 맞는 함수의 주소를 가리킨다.

위 코드에서 bptr의 Derived Class의 가상함수 주소를 가리킴

 

(1) 가상함수 규칙

① static과 friend 키워드를 사용할 수 없다.

② 기본클래스의 포인터를 통해 기본클래스를 참조한 기반 클래스의 오브젝트에 접근해야한다.(기반클래스의 객체가 기본클래스의 객체를 참조할 수 없다.)

③ 재정의 시(override) 기본클래스의 정의한 가상함수 타입이 동일해야한다.

④ 기본 클래스에서 가상함수를 사용하고 소멸자를 사용하게 된다면 기본 클래스에서 가상소멸자를 꼭 선언해야한다.

⑤ 기본 클래스에서 virtual function을 사용하게 된다면 기반클래스에서도 virtual function이 적용된다.

 

(2) 가상 소멸자를 꼭 사용해야 하는 이유

기본 클래스의 포인터에서 기반 클래스의 객체를 가리키고 나서 자원을 해제 하게 될 경우 기반클래스의 자원을 해제하지 못해 자원의 누수가 발생될 수 있다. 이를 방지 하기 위해 기본클래스에서 가상소멸자를 선언하여 기반클래스의 자원까지 해제하도록 해야한다.

class Base{
public:
    ~Base() {
        cout << "Base destructor!" << endl;
    }
};

class Derived : public Base{
public:
    char* largeBuffer;
    Derived() {
        largeBuffer = new char[3000];
    }

    ~Derived() {
        cout << "Derived destructor!" << endl;
        delete[] largeBuffer;
    }
};

int main(){
    //코드1
    cout << "---Derived* der1 = new Derived()---" << endl;
    Derived* der1 = new Derived();
    delete der1;

    //코드2
    cout << "\n\n---Base* der2 = new Derived()---" << endl;
    Base* der2 = new Derived();
    delete der2;

}

위 코드에서 der1의 객체는 기본클래스와 기반클래스의 자원을 둘 다 해제하지만, der2객체는 기반클래스(Derived)의 자원을 해제하지 못해 메모리누수가 발생하게 되고 이를 방지하기 위해 기본클래스의 소멸자에서 virtual ~Base()를 선언해야한다.

 

(3) 가상함수 매개변수에 디폴트 값을 설정한 경우

#include <iostream> 
using namespace std;

class Base
{
public:
	virtual void fun(int x = 12)
	{
		cout << "Base::fun(), x = " << x << endl;
	}
};

class Derived : public Base
{
public:
	virtual void fun(int x = 10)
	{
		cout << "Derived::fun(), x = " << x << endl;
	}
};


int main()
{
	Derived d1;
	Base* bp = &d1;
	bp->fun();	// Derived::func(), x = 12
	return 0;
}

위 코드에서 기본클래스의 default로 설정한 매개변수의 값이 출력되는 이유는 compile시간에 매개변수 x에 대한 default값을 설정하고, compiler는 bp의 기본 타입인 Base의 맞게 Defalut값을 설정한다. 즉 virtual 함수를 호출하는 건 runtime시간에 일어나지만, 매개변수의 setting은 compile 시간에 발생하여 기본클래스에서 세팅한 값이 출력된다.

 

(5) 순수 가상 소멸자

순수 가상 소멸자를 사용하는 이유는 소멸자를 강제로 정의하기 위해 사용된다. 즉 소멸자를 정의하지 않는 경우를 방지하기 위해 쓰인다. 또한 순수 가상함수를 포함한 클래스는 추상클래스가 된다.

#include <iostream> 
class Base 
{ 
public: 
	virtual ~Base()=0; // Pure virtual destructor 
}; 
Base::~Base() 
{ 
	std::cout << "Pure virtual destructor is called"; 
} 

class Derived : public Base 
{ 
public: 
	~Derived() 
	{ 
		std::cout << "~Derived() is executed\n"; 
	} 
}; 

int main() 
{ 
	Base *b = new Derived(); 
	delete b; 
    // ~Derived() is executed
    // Pure virtual destructor is called
	return 0; 
}

'프로그래밍 > C & C++' 카테고리의 다른 글

Stack Unwinding(스택 되감기)  (0) 2020.04.24
Smart Pointer  (0) 2020.04.22
Dangling Pointer(허상 포인터)  (0) 2020.04.17
Vector 컨테이너  (0) 2020.04.09
STL Vector와 List의 차이점  (0) 2020.03.25
더보기

댓글,

야미야미얌얌

프로그래밍 및 IT 기술