티스토리 뷰
주의 사항!
- 이 글은 제가 직접 공부하는 중에 작성되고 있습니다.
- 따라서 제가 이해하는 그대로의 내용이 포함됩니다.
- 따라서 이 글은 사실과는 다른 내용이 포함될 수 있습니다.
가상 함수 말고도 virtual 키워드를 붙여줘야 할 대상이 하나 더 있습니다. 바로 소멸자입니다. 즉, virtual 선언은 소멸자에도 올 수 있습니다. 먼저 다음의 예제를 살펴보겠습니다.
#include <iostream>
#include <cstring>
using namespace std;
class First
{
private:
char* strOne;
public:
First(const char* str)
{
strOne = new char[strlen(str) + 1];
strcpy(strOne, str);
cout << "Frist" << endl;
}
~First()
{
cout << "~First" << endl;
delete[] strOne;
}
};
class Second : public First
{
private:
char* strTwo;
public:
Second(const char* str1, const char* str2) : First(str1)
{
strTwo = new char[strlen(str2) + 1];
strcpy(strTwo, str2);
cout << "Second" << endl;
}
~Second()
{
cout << "~Second" << endl;
delete[] strTwo;
}
};
int main(void)
{
First* ptr = new Second("simple", "complex");
delete ptr;
return 0;
}
/*
실행결과
Frist
Second
~First
*/
위 예제를 보면, First 클래스와 Second 클래스를 정의했고, Second 클래스가 First 클래스를 상속받도록 하였습니다. 그리고 main 함수에서는 First형 포인터 변수에 Second 객체의 주소를 저장했고, 이 포인터를 이용해서 객체를 소멸시켰습니다.
그런데 실행결과를 보면 First의 소멸자만 호출이 되었음을 확인할 수 있습니다. Second 객체를 동적으로 할당했기 때문에 First와 Second 모두 생성자가 호출되었지만, 이후 객체를 소멸시킬 때는 포인터를 통해 소멸시켰으므로 포인터의 자료형을 따라 소멸처리를 하게 됩니다. 따라서 First 소멸자만 호출이 되었습니다.
이렇게 되면 Second 객체가 생성될 때 동적 할당된 메모리 공간이 제대로 반환되지 못하기 때문에 메모리 누수가 발생하게 됩니다. 따라서 객체의 소멸과정에서는 delete 연산자에 사용된 포인터 변수의 자료형에 상관없이 모든 소멸자가 호출되어야 합니다. 그리고 이를 위해서는 virtual 선언을 추가하면 됩니다.
virtual ~First()
{
......
}
가상 함수와 마찬가지로 소멸자도 상속의 계층 구조상 맨 위에 존재하는 기초 클래스의 소멸자만 virtual로 선언하면, 이를 상속하는 유도 클래스의 소멸자들도 모두 (별도로 virtual 선언을 추가하지 않아도) '가상 소멸자'로 선언이 됩니다.
가상 소멸자를 사용하여 위 예제를 수정해보겠습니다.
#include <iostream>
#include <cstring>
using namespace std;
class First
{
private:
char* strOne;
public:
First(const char* str)
{
strOne = new char[strlen(str) + 1];
strcpy(strOne, str);
cout << "Frist" << endl;
}
virtual ~First()
{
cout << "~First" << endl;
delete[] strOne;
}
};
class Second : public First
{
private:
char* strTwo;
public:
Second(const char* str1, const char* str2) : First(str1)
{
strTwo = new char[strlen(str2) + 1];
strcpy(strTwo, str2);
cout << "Second" << endl;
}
~Second()
{
cout << "~Second" << endl;
delete[] strTwo;
}
};
int main(void)
{
First* ptr = new Second("simple", "complex");
delete ptr;
return 0;
}
*/
실행결과
Frist
Second
~Second
~First
*/
First 클래스의 소멸자에 virtual 선언을 추가했습니다. 그러면 main 함수에서 똑같이 First형 포인터인 ptr을 통해 delete 연산을 수행할 때, First 클래스의 가상 소멸자를 확인하고, 상속의 계층 구조 상 가장 아래에 존재하는 유도 클래스의 소멸자가 대신 호출됩니다. 그리고 기초 클래스의 소멸자가 순차적으로 호출됩니다.
포인터와 같이 참조자에도 똑같이 적용되는 virtual 키워드
앞서 객체 포인터에 대해서 배웠습니다. 객체 포인터가 어떤 대상을 가리킬 수 있는지, 그리고 객체 포인터를 통해 유도 클래스의 멤버에는 접근하지 못한다는 것, virtual 키워드를 사용하면 실제 가리키는 객체의 멤버에 접근할 수 있다는 것 등을 알게 되었습니다.
이 모든 것은 참조자에 대해서도 성립합니다. 사용법도 객체 포인터와 동일합니다. 따라서 객체 포인터를 통해 했던 모든 든 것들이 참조자를 이용해서도 가능하다는 것을 알고 있으면 됩니다.
'공부 일지 > CPP 공부 일지' 카테고리의 다른 글
C++ | 다중 상속(Multiple Inheritance) (0) | 2021.08.03 |
---|---|
C++ | 멤버 함수와 가상 함수의 동작 원리 (0) | 2021.08.03 |
C++ | 가상 함수(Virtual Function) (0) | 2021.08.03 |
C++ | 객체 포인터와 다형성 (0) | 2021.08.02 |
C++ | 상속 (0) | 2021.08.02 |