[문과 코린이의 IT 기록장] C,C++ -상속과 다형성 3 : 가상 소멸자와 참조자의 참조 가능성
(가상 소멸자, 참조자의 참조 가능성)
1. 가상 소멸자
- 가상함수 이외에도, virtual 키워드를 붙여줘야 할 대상이 있다. 바로 소멸자이다.
- virtual로 선언된 소멸자를 가리켜, '가상 소멸자'라 한다.
ex 1 ) 문제점 이해하기
#include <iostream>
#include <cstring>
using namespace std;
class First{ // First 클래스
private:
char * strOne;
public:
First(const char *str){ // First 생성자
strOne = new char[strlen(str)+1]; // 생성자 내부의 동적할당
}
~First(){ // First 소멸자
cout<<"~First()"<<endl;
delete [] strOne; // 생성자 동적할당 소멸
}
};
class Second : public First{ // Second 클래스
private:
char * strTwo;
public:
Second(const char * str1, const char * str2):First(str1){ // Second 생성자
strTwo = new char[strlen(str2)+1];
}
~Second(){ // Second 소멸자
cout<<"~Second()"<<endl;
delete [] strTwo;
}
};
int main(){
First * ptr = new Second("simple","complex"); // 객체 생성
delete ptr; // ptr 소멸 (First 소멸자, Second 소멸자 동시 호출 필요)
return 0;
}
- 실행결과를 보면, ~First() 만 호출된 것을 볼 수 있다. : 객체의 소멸을 First형 포인터로 명령했기 때문에 발생하는, 메모리 누수 현상이다. : 그러나, 객체의 소멸과정에서는 delete 연산자에 사용된 포인터 변수의 자료형에 상관없이, 모든 소멸자가 호출되어야 한다. |
- 이를 해결하기 위해서는, 소멸자에 virtual 선언을 추가하면 된다.
virtual ~First(){ cout<<"~First()"<<endl; delete [] strOne; } |
- 가상함수와 마찬가지로, 소멸자도 상속의 계층구조상 맨 위에 존재하는 기초 클래스의 소멸자만 virtual로 선언하면, 상속하는 유도 클래스의 소멸자들도 모두 '가상 소멸자'로 선언이 된다.
ex 2 ) 해결 방안
class First{
...
public:
virtual ~First() { ... }
};
class Second : public First{
...
public:
virtual ~Second() { ... }
};
class Third : public Second{
...
public:
virtual ~Third(){ ... }
};
int main(){
First * ptr = new Third();
delete ptr;
...
}
- 이 코드의 경우, 아래의 과정을 거치면서 모든 소멸자가 호출된다.
2. 참조자의 참조 가능성
- 이전에 이야기 했던, 포인터/상속과 관련해 알아야 할 점.
1) C++에서, AAA형 포인터 변수는, AAA객체 또는 AAA를 직/간접적으로 상속하는 모든 객체를 가리킬 수 있다. (객체의 주소 값을 저장할 수 있다.) 2) First형 포인터 변수를 이용하면 First 클래스에 정의된 MyFunc() 함수가 호출되고, Second형 포인터 변수를 이용하면 Second 클래스에 정의된 MyFunc() 함수가 호출되고, Third형 포인터 변수를 이용하면 Third클래스에 정의된 MyFunc() 함수가 호출된다. |
- 위의 특성들은 참조자의 경우에도 적용이 된다. 즉 이와 같은 문장도 성립한다는 것이다.
1) C++에서 AAA형 참조자는, AAA 객체 또는 AAA를 직/간접적으로 상속하는 모든 객체를 참조할 수 있다. 2) First형 참조자를 이용하면 First클래스에 정의된 MyFunc() 함수가 호출되고, Second형 참조자를 이용하면 Second클래스에 정의된 MyFunc() 함수가 호출되고, Third형 참조자를 이용하면 Third 클래스에 정의된 MyFunc() 함수가 호출된다. |
ex )
#include <iostream>
using namespace std;
class First{ // First 클래스
public:
void FirstFunc() { cout<<"FirstFunc()"<<endl; }
virtual void SimpleFunc() { cout<<"First's SimpleFunc()"<<endl;} // 가상함수 SimpleFunc()
};
class Second : public First{ // Second 클래스
public:
void SecondFunc() { cout<<"SecondFunc()"<<endl; }
virtual void SimpleFunc() { cout<<"Second's SimpleFunc()"<<endl;}
};
class Third : public Second{ // Third 클래스
public:
void ThirdFunc() { cout<<"ThirdFunc()"<<endl; }
virtual void SimpleFunc() { cout<<"Third's SimpleFunc()"<<endl; }
};
int main(){
Third obj; // Third형 객체 obj 생성
obj.FirstFunc();
obj.SecondFunc();
obj.ThirdFunc();
obj.SimpleFunc();
Second & sref = obj; // Second형 참조자는, Second/Third형 객체를 참조할 수 있다.
sref.FirstFunc();
sref.SecondFunc(); // 컴파일러는 참조자의 자료형을 가지고 함수의 호출 가능성을 판단한다. 따라서, Second형 참조자이기 때문에, ThirdFunc()는 실행할 수 없다.
sref.SimpleFunc(); // SimpleFunc()는 가상함수이기 때문에, 실제 객체인 Third형을 따라가서 Third 클래스에 정의된 SimpleFunc() 함수를 호출한다.
First & fref = obj; // First형 참조자는, First/Second/Third형 객체를 참조할 수 있다
fref.FirstFunc(); // 컴파일러는 참조자의 자료형을 가지고 함수의 호출 가능성을 판단한다. 따라서, First형 참조자이기 때문에, SecondFunc(), ThirdFunc()는 실행할 수 없다.
fref.SimpleFunc(); // SimpleFunc()는 가상함수이기 때문에, 실제 객체인 Third형을 따라가서 Third 클래스에 정의된 Simplefunc() 함수를 호출한다.
return 0;
}
- 위의 예제를 이해했다면, 다음의 함수에 대한 해석에 대해서도 생각해보자.
Void Function(const First & ref) { ... }
a. First 객체 또는 First를 직/간접적으로 상속하는 클래스의 객체가 인자의 대상이 된다. b. 인자로 전달되는 객체의 실제 자료형에 상관없이, 함수 내에서는 First 클래스에 정의된 함수만 호출 될 수 있을 것이다. c. 그러나, 가상함수의 경우에는 실제 객체의 자료형을 가지고 판단하기 때문에, 호출 가능하다. |
* 유의사항 - 아직 공부하고 있는 문과생 코린이가, 정리해서 남겨놓은 정리 및 필기노트입니다. - 정확하지 않거나, 틀린 점이 있을 수 있으니, 유의해서 봐주시면 감사하겠습니다. - 혹시 잘못된 점을 발견하셨다면, 댓글로 친절하게 남겨주시면 감사하겠습니다 :) |