C++ 에서의 상속과 Virtual 키워드 c++




상속에 있어서 생성자/소멸자의 순서라던가 virtual 키워드에 관한 것을 정리해보려한다. 사실 상속과 virtual 의 유형에 대해서 제대로 배웠다면 대부분.. 혹은 전부가 이미 알 수 있는 것들이지만, 한번쯤을 헷깔릴 수도 있는 부분인 것 같아서 정리 차원에서 포스팅한다.

기본 클래스 (Language : cpp)
  1. class BaseClass
  2. {
  3. public:
  4.     BaseClass()
  5.     {
  6.         printf( "Constructor of Base Class\n" ); // (1-1)
  7.     }
  8.     ~BaseClass()
  9.     {
  10.         printf( "Destructor of Base Class\n" );  // (1-2)
  11.     }
  12.     void MemberFunction()
  13.     {
  14.         printf( "Member Function of Base Class\n" ); //  (1-3)
  15.     }
  16. };
  17.  
  18. class ChildClass : public BaseClass
  19. {
  20. public:
  21.     ChildClass()
  22.     {
  23.         printf( "Constructor of Child Class\n" ); // (2-1)
  24.     }
  25.     ~ChildClass()
  26.     {
  27.         printf( "Destructor of Child Class\n" ); // (2-2)
  28.     }
  29.     void MemberFunction()
  30.     {
  31.         printf( "Member Function of Child Class\n" ); // (2-3)
  32.     }
  33. };

생성자, 소멸자, 멤버함수로 똑같이 구성되어 있는 함수가 2개있다. 그중 ChildClass 는 BaseClass 로부터 상속한다.

그냥 보면 재미가 없을테니, 퀴즈형식으로 맞춰 봐도 좋을 듯 하다. printf 문에 1-1 부터 2-3 까지 번호를 부여했다. 각 문제에 대해 printf 문이 출력되는 순서는 어떻게 될까?(소멸자 호출도 중요!!)

1. Quiz 1
(Language : cpp)
ChildClass C;
C.MemberFunction();

2. Quiz 2
(Language : cpp)
ChildClass *pC = new ChildClass;
pC->MemberFunction();
delete pC;

3. Quiz 3
(Language : cpp)
BaseClass *pP;
pP = new ChildClass;
pP->MemberFunction();
delete pP;

4. Quiz 4
BaseClass 의 소멸자 부분(8번째 줄)을 virtual ~BaseClass() 로 바꾸어 virtual 로 선언한 후의 Quiz 3 과 같은 소스.





정답 ....

1. Quiz 1 - 정답
Constructor of Base Class (1-1)
Constructor of Child Class (2-1)
Member Function of Child Class (2-3)
Destructor of Child Class (2-2)
Destructor of Base Class (1-2)

2. Quiz 2 - 정답
Constructor of Base Class (1-1)
Constructor of Child Class (2-1)
Member Function of Child Class (2-3)
Destructor of Child Class (2-2)
Destructor of Base Class (1-2)

3. Quiz 3 - 정답
Constructor of Base Class (1-1)
Constructor of Child Class (2-1)
Member Function of Base Class (3-3)
Destructor of Base Class (1-2)

4. Quiz 4 - 정답
Constructor of Base Class (1-1)
Constructor of Child Class (2-1)
Member Function of Base Class (3-3)
Destructor of Child Class (2-2)
Destructor of Base Class (1-2)

이중에 그나마 틀리게 되는 부분은 소멸자 부분이다.
3, 4번에서는 BaseClass 로 포인터를 만들고 실제 객체화(instantiation)는 ChildClass 로 되어 있다.
virtual 을 쓰지 않은 3번에서는 본래 포인터의 type 인 Base Class 의 소멸자만 호출되었다는 것이 특징이고,
virtual 을 쓴 4번에서는 바로 ChildClass 로 생성한 것과 마찬가지로 Child, Base 순으로 소멸자가 호출되었다.

사실상 3번의 방법은 사용하면 안된다.
단순히 ChildClass 의 소멸자가 호출되지 않아서가 아니라, 3번의 방법은 메모리 누수가 일어날 가능성이 있다.
즉, 실제로 객체화(instantiation)는 ChildClass 로 되어 있기 때문에 메모리도 ChildClass 로 잡혀 있다. 하지만, 3번의 방법으로는 소멸자가 이 객체를 BaseClass 로 생각하고 메모리를 해제하기 때문에 그 메모리 차이만큼 메모리 누수가 일어난다.

여기서의 특징은 멤버함수는 본래의 BaseClass 의 것으로 호출되었다는 것이다.
ChildClass 멤버함수를 호출하기 위해서는 BaseClass 를
virtual void BaseClass 로 변경하면 된다.

또 하나의 특징은 추상화(Abstraction)인데, C++에서는 abstraction 을 직접적으로 사용하지 않는다. 단지, 위의 BaseClass 의 멤버 함수를 virtual void MemberFunction() = 0; (실제 함수 내용은 있어서는 안된다.) 로 선언을 할 경우 이 BaseClass 자체는 Abstraction Class 가 된다. 이 말은, 이 함수를 실제로 직접적으로 객체화할 수 없고, 다른 클래스를 통해서 상속이 된 후에 사용되어야 한다는 의미이다. 그때 그 Child Class 는 반드시 이 MemberFunction() 함수를 정의시켜야 한다.

사실 위의 내용은 어느정도 C++에 익숙한 사람이라면 다 아는 내용이라 포스팅하기 전에 좀 고민했다. 하지만 일단 내 경우는 Quiz 4 번에서 virtual 로 클래스를 소멸할때 
Destructor of Child Class (2-2)
Destructor of Base Class (1-2)
로 두번에 걸쳐 소멸된다는 것은 모르고 있었다. Child 의 소멸자만 호출되는 것으로 알고 있었다.(이것을 몰랐던 이유는, 사실 이것때문에 문제가 된 적이 없기 때문이다. 소멸자에서 하는 메모리 해제도 모두 중복 해제를 고려해서 만들기 때문에 역시 문제가 된적이 없었다.)

마지막으로 위의 내용이나 이것을 넘어선 팁은 Effective C++ 책을 참고하면 좋을듯 하다. 볼만한 관련된 내용들이 많다. 단순히 사용법을 떠나 주의해야 하는 내용들이 담겨 있다. 







덧글

댓글 입력 영역

구글와이드