[Effective C++] #7 기본 클래스와 가상 소멸자
Scott Meyers의 "Effective C++"를 통해, C++ 구현에 필요한 개념들을 이해하고, 기록하기 위함입니다. 해당 항목은 2장 "생성자, 소멸자 및 대입 연산자", 항목 7 "다형성을 가진 기본 클래스에서는 소멸자를 반드시 가상 소멸자로 선언하자"에 해당하는 내용입니다.
가상 소멸자
virtual ~Class_Name( ) {...}
"소멸자"란 객체의 소멸 시점에 자원을 정상적으로 해제하기 위해 호출하는 함수입니다. 이 소멸자를 "virtual" 키워드와 함께 사용하는 것을 "가상 소멸자"라고 합니다. "가상 소멸자"는 호출될 때, 다형성을 지원하는 기본 클래스를 상속받는 자식 클래스의 소멸자를 호출 한 뒤에 부모 클래스의 소멸자를 호출하는, 일종의 순차적인 "소멸자" 호출을 지원합니다. 밑에서 자세히 살펴보겠습니다.
비가상 소멸자 사용으로 인한 자원 누출의 위험
class 기본클래스 {
public:
기본클래스 ();
~기본클래스 ();
...
};
기본클래스* Get기본클래스 ();
class 자식클래스_1: public 기본클래스 {...};
class 자식클래스_2: public 기본클래스 {...};
class 자식클래스_3: public 기본클래스 {...};
위 예제 코드는 "기본 클래스"의 다형성을 표현합니다. 세 개의 자식 클래스들이 "기본 클래스"를 public 상속합니다. 만약, 사용자가 기본 클래스의 일부 정보를 필요로 한다면, 우리는 "팩토리 함수"를 정의하겠죠. 이때, 팩토리 함수는 새롭게 생성된 파생클래스에 대한 기본 클래스 형식의 포인터를 반환하는 함수를 말합니다. 팩토리 함수로부터 반환되는 객체는 "힙"에 동적으로 할당되며, 메모리 및 자원 누출을 방지하기 위해 동적으로 할당된 객체들을 프로그램 종료 시점에 적절히 해제해야 합니다.
하지만, 기본 클래스의 "소멸자"가 "비 가상 소멸자"일 경우 문제가 발생합니다. 그 이유는 파생 클래스에 대한 포인터가 기본 클래스 형식의 포인터이기 때문입니다. 포인터가 가리키는 파생 클래스 객체가 삭제될 때, 기본 클래스가 삭제를 떠맡게 되는데, 기본 클래스의 "소멸자"가 "비 가상 소멸자"라면, 동적 할당된 파생 클래스 객체는 정상적으로 자원 해제될 수 없겠죠. 쉽게 얘기해서, 파생 클래스 객체의 정상적인 삭제를 위해 기본 클래스 부분과 파생 클래스 부분 둘 다 삭제되어야 하지만, 기본 클래스의 "비 가상 소멸자"때문에 기본 클래스만 소멸되고, 파생 클래스는 소멸되지 않는 거죠. 이러한 현상을 미연에 방지하기 위해 우리는 기본 클래스에 가상 소멸자를 사용해야 합니다.
쉽게 생각해서, 가상 소멸자는 기본 클래스와 짝을 이룹니다.
가상 함수와 가상 소멸자
class Point {
public:
Point(int xValue, int yValue);
~Point();
private:
int x, y;
};
"가상 소멸자"의 무분별한 사용 또한 우리가 주의해야 합니다. 쉽게 말해, 기본 클래스가 아닌 클래스에 "가상 소멸자"를 사용하는 것은 고려해볼 점이 많습니다. 위 예제 코드를 살펴보겠습니다. "Point" 클래스의 데이터 멤버는 정수 x와 y입니다. 따라서, 이 Point 클래스의 객체는 64bit 레지스터에 딱 들어맞게 됩니다. 하지만, Point 클래스의 소멸자가 만약 가상 소멸자라면 상황이 많이 달라집니다. C++에서 가상 함수를 구현한다는 것은 클래스에 별도의 자료구조를 필요로 합니다. 보통 이 자료구조는 가상 함수 테이블에 대한 포인터를 뜻합니다. 결과적으로, Point 객체의 크기가 우리가 예상한 64bit보다 훨씬 커집니다(자세한 내용은 찾아보셔도 괜찮을 내용입니다). 따라서, 우리는 가상 함수를 가지는 클래스에만 "가상 소멸자"를 사용하도록 합시다.
추상 클래스와 순수 가상 소멸자
class 추상클래스 {
public:
virtual ~추상클래스 () = 0;
};
잘 알다시피, 추상 클래스는 자체적으로 인스턴스를 생성 할 수 없죠. 추상 클래스는 또한 기본 클래스의 성격을 가집니다. 위에서 설명한 대로, 기본 클래스는 가상 소멸자를 필요로 합니다. 결국, 추상 클래스는 "순수 가상 소멸자"를 필요로 합니다.
가상 함수는 "다형성"을 대변합니다. 따라서, 가상 함수를 포함하는 클래스에는 "가상 소멸자"를 사용합시다.
'언어 > Effective C++' 카테고리의 다른 글
[Effective C++] #9 가상 함수를 호출하는 생성자, 소멸자 (0) | 2022.01.10 |
---|---|
[Effective C++] #8 소멸자가 던지는 예외 (0) | 2022.01.10 |
[Effective C++] #6 사용자 정의 기본 멤버 함수 (0) | 2022.01.07 |
[Effective C++] #5 생성자, 소멸자, 복사 생성자, 복사 대입 연산자 (0) | 2022.01.06 |
[Effective C++] #4 객체의 초기화 (0) | 2022.01.06 |