KoreanFoodie's Study

Effective C++ | 항목 7 : 다형성을 가진 기본 클래스에서는 소멸자를 반드시 가상 소멸자로 선언하자 본문

Tutorials/C++ : Advanced

Effective C++ | 항목 7 : 다형성을 가진 기본 클래스에서는 소멸자를 반드시 가상 소멸자로 선언하자

GoldGiver 2022. 10. 25. 16:05

C++ 프로그래머의 필독서이자 바이블인, 스콧 마이어스의 Modern Effective C++ 를 읽고 기억할 내용을 요약하고 있습니다. 꼭 읽어보시길 추천드립니다!

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

핵심 :

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


다음과 같은 TimeKeeper 클래스를 만들고, 팩토리 메서드 패턴으로 파생 클래스의 타입에 맞는 객체를 리턴한다고 가정하자.

class TimeKeeper
{
  public:
    TimeKeeper();
    ~TimeKeeper();
    
    ...
};

class AtomicClock: public TimeKeeper { ... };
class WaterClock: public TimeKeeper { ... };
class WristClock: public TimeKeeper { ... };

...

// 팩토리 메서드 패턴
TimeKeeper* ptk = getTimeKeeper();

...

// 메모리 해제 (소멸자 호출)
delete ptk;

C++ 규정에 따르면, 기본 클래스의 소멸자가 비가상 소멸자(non-virtual destructor) 일 경우, 파생 클래스 객체가 삭제될 때 프로그램의 동작은 미정의 사항이다. 일반적으로는 그냥 파생 클래스의 소멸자가 실행되지 않는다고 보면 된다.
이 문제는 소멸자를 가상으로 만들어 줆으로써 간단히 해결할 수 있다.

virtual ~TimeKeeper()


반대로, 기본 클래스로 의도하지 않은 클래스에 대해 소멸자를 가상으로 선언하는 것은 좋지 않다. 예시를 보자.

class Point
{
  public:
    Point(int xCoord, int yCoord);
    ~Point();
    
  private:
    int x, y;
};

위의 경우에서, Point 클래스 객체의 크기는 일반적으로 64 비트라고 가정하고 사용할 수도 있을 것이다. 하지만 가상함수가 생기게 되면, 이를 호출하기 위한 vptr(가상 함수 테이블 포인터, virtual table pointer) 를 추가로 만들어야 하는데, 그렇게 되면 객체의 크기가 뻥튀기가 된다. 즉, 기존처럼 64 비트 레지스터에 넣을수도 없고, 이식성도 떨어진다.
따라서, 소멸자를 virtual 로 선언하는 일은 가상 함수가 하나라도 들어 있는 경우에만 한정하는 것이 좋다.

또한, 가상함수가 없는데 비가상 소멸자 때문에 뒤통수를 맞는 경우도 있다. 다음의 예시를 보자.

// std::string 은 가상 소멸자가 없다!
class SpecialString: public std::string { ... };

SpecialString *pss = new SpecialString("Doom");
std::string* ps;
ps = pss;

// 정의되지 않은 동작이 발생!
// *ps 의 SpecialString 부분 자원 누출
// SpecialString 의 소멸자가 호출되지 않기 때문!
delete ps;

위와 같은 현상은 가상 소멸자가 없는 클래스이면 어떤 것에든 전부 적용된다. STL 컨테이너 타입(vector, list, set 등) 전부가 여기에 속한다.

경우에 따라서는 순수(pure) 가상 소멸자를 두면 편리할 수도 있다. 즉, 추상 클래스로 만들고 싶은 클래스에 순수 가상 소멸자를 선언하는 것이다!

// AWOV = "Abstract w/o Virtuals"
class AWOV
{
  // 순수 가상 소멸자 선언
  virtual ~AWOV() = 0;
};;

// 순수 가상 소멸자 정의
AWOV::~AWOV() {}

 

Comments