가상함수(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된 클래스의 타입을 고려하여 가상함수 테이블에서 타입에 맞는 함수의 주소를 가리킨다.
(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;
}
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 기술