본문 바로가기

Effective C++

[EC++] 항목 7. 다형성을 가진 기본 클래스에서는 소멸자를 반드시 가상 소멸자로 선언하자.

  Effective C++에 있는 내용이라고 겁먹지 말자. 우리는 이미 이 내용을 제가 C++ 포스팅 하면서 언급을 한 적이 있습니다. 왜 다형성을 가진 기본 클래스에서는 소멸자를 가상 소멸자로 써야 하는가? 우선 다형성과 그 이유에 대해서 먼저 보시죠.



  위 두 포스팅만 보더라도 왜 가상 소멸자를 쓰는지 이유는 충분히 알 수 있을 것입니다.  가상 함수를 C++에서 구현하려면 클래스에 별도의 자료구조가 하나 들어가야 합니다. 이 자료구조는 프로그램 실행 중에 주어진 객체에 대해 어떤 가상 함수를 호출해야 하는지는 결정하는 데 쓰이는 정보인데, 실제로는 포인터의 형태를 취하는 것이 대부분이고, 이를 가상 함수 테이블 포인터(Virtual table pointer) 즉, vptr이라는 이름으로 부릅니다. vptr은 가상 함수의 주소, 포인터들의 배열을 가리키고 있으며 가상 함수 테이블 포인터의 배열은 가상 함수 테이블(virtual table). vtbl이라고 부릅니다.  가상 함수를 하나라도 갖고 있는 클래스는 반드시 그와 관련된 vtbl을 갖고 있습니다. 어떤 객체에 대해 어떤 가상 함수가 호출되려고 하면, 호출되는 실제 함수는 그 객체의 vptr이 가리키는 vtbl에 따라 결정이 됩니다. vtbl에 있는 함수 포인터들 중 적절한 것이 연결된다는 의미이죠. 

  지금까지 가상 소멸자에 대해서만 이야기 했는데, 순수 가장 소멸자(pure virtual destructor)는 어떨까요? 순수 가상 함수는 해당 클래스를 추상 클래스(abstract class)로 만듭니다. 하지만 어떤 클래스가 추상 클래스였으면 좋겠는데 마땅히 넣을 만한 순수 가상 함수가 없을 때도 생기게 마련인데요. 이럴 때에는 어떻게 할까요?
 추상클래스는 본래 기본 클래스로 쓰일 목적으로 만들어진 것이고, 기본 클래스로 쓰이려는 클래스는 가상 소멸자를 가져야 합니다. 한편 순수 가상 함수가 있으면 바로 추상 클래스가 되죠. 따라서, 추상 클래스로 만들고 싶은 클래스에 순수 가상 소멸자를 선언하는 것입니다. 
 위의 AWOV 클래스는 순수 가상 함수를 가지고 있고, 이 순수 가상 함수가 가상 소멸자 이므로, 앞에서 말한 소멸자 호출 문제로 고민할 필요는 없는 것이지만, 이 순수 가상 소멸자의 정의를 두지 않으면 안됩니다. 
 소멸자의 동작 순서를 보자면, 상속 구조에서 가장 말단에 있는 파생 클래스의 소멸자가 먼저 호출되고, 기본 클래스 쪽으로 거쳐 올라가면서 각 기본 클래스의 소멸자가 하나씩 호출이 됩니다. 컴파일러는 ~AWOV의 호출 코드를 만들기 위해 파생 클래스의 소멸자를 사용할 것이므로, 위 함수의 본문을 준비해 두어야 합니다. (이 부분이 없다면 링커 에러가 발생합니다.)
  모든 기본 클래스가 다형성을 갖도록 설계된 것은 아닙니다. 예를 들어 표준 string 타입이나 STL 컨테이너 타입은 기본 클래스는 커녕 다형성의 흔적조차 없기 때문이죠. 한편, 기본 클래스로는 쓰일 수 있지만 다형성은 갖지 않도록 설계된 클래스도 있는데 이런 클래스에서 이들에게서 가상 소멸자를 볼 수 없는 것은 기본 클래스의 인터페이스를 통한 파생 클래스 객체의 조작이 허용되지 않기 때문이죠.  

 * 다형성을 가진 기본 클래스에는 반드시 가상 소멸자를 선언해야 합니다. 즉, 어떤 클래스가 가상 함수를 하나라도 갖고 있으면, 이 클래스의 소멸자도 가상 소멸자이어야 합니다. 
 * 기본 클래스로 설계되지 않았거나 다형성을 갖도록 설계되지 않은 클래스에는 가상 소멸자를 선언하지 말아야 합니다.