문과 코린이의, [C. C++] 기록/C++ 이론

[문과 코린이의 IT 기록장] C,C++ -상속과 다형성 3 : 가상 소멸자와 참조자의 참조 가능성 (가상 소멸자, 참조자의 참조 가능성)

벼리네 2021. 3. 8. 21:25
반응형

[문과 코린이의 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"); // 객체 생성

new & delete 연산자에 대해 더 알고싶다면?


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. 그러나, 가상함수의 경우에는 실제 객체의 자료형을 가지고 판단하기 때문에, 호출 가능하다.

* 유의사항
- 아직 공부하고 있는 문과생 코린이가, 정리해서 남겨놓은 정리 및 필기노트입니다.
- 정확하지 않거나, 틀린 점이 있을 수 있으니, 유의해서 봐주시면 감사하겠습니다.
- 혹시 잘못된 점을 발견하셨다면, 댓글로 친절하게 남겨주시면 감사하겠습니다 :)
반응형