본문 바로가기

Effective C++ 정리

07. 다형성을 가진 기본 클래스 소멸자는 반드시 가상 소멸자로 선언하자

먼저 부모 클래스 소멸자에 virtual이 없는 코드입니다.

 

#include <iostream>

class Base
{
public:
	Base()
	{
		std::cout << "부모 생성자" << std::endl;
	}

	~Base()
	{
		std::cout << "부모 소멸자" << std::endl;
	}
};

class Deprived :
	public Base
{
public:
	Deprived()
	{
		std::cout << "자식 생성자" << std::endl;
	}

	~Deprived()
	{
		std::cout << "자식 소멸자" << std::endl;
	}
};

 

그리고 다이나믹 프로그래밍을 위한 자식 객체를 생성합니다. 

int main()
{
	// 자식 객체 동적 메모리 할당
	CBase* pBase = new CDeprived;

	// 자식 객체 메모리 해제
	delete pBase;
    
	return 0;
}

 

 

 

자식 소멸자가 호출되지 않았습니다. 기본 클래스 소멸자에 virtual을 붙여서 가상 소멸자를 정의해보겠습니다.

 

#include <iostream>

class Base
{
public:
	Base()
	{
		std::cout << "부모 생성자" << std::endl;
	}

	// 기본 클래스의 소멸자를 가상 소멸자로 만든다.
	virtual ~Base()
	{
		std::cout << "부모 소멸자" << std::endl;
	}
};

class Deprived :
	public Base
{
public:
	Deprived()
	{
		std::cout << "자식 생성자" << std::endl;
	}

	~Deprived()
	{
		std::cout << "자식 소멸자" << std::endl;
	}
};

int main()
{
	Base* pBase = new Deprived;

	delete pBase;

	return 0;
}

 

 

자식 소멸자까지 제대로 호출되었습니다.

 

C++ 규정에 의하면 자식 객체를 가리키는 기본 클래스 포인터를 통해서 자식 객체가 제거 될 때,

기본 클래스가 비가상 소멸자라면 프로그램의 동작은 미정의 사항이라고 합니다.

 

다이나믹 프로그래밍에서 기본 클래스 타입의 포인터로 자식 객체를 제거할 수 있다는 뜻이군요.

기본 클래스의 가상 소멸자를 통해 자식 소멸자까지 제대로 호출하는 것이 중요하겠습니다.

추상 클래스 기반의 상속도 마찬가지로 순수 가상 소멸자로 정의합니다.

 

기본 클래스로부터 다형성의 의도가 없다면 가상 소멸자를 선언하지 않는다

다형성의 의도가 없는 기본 클래스 소멸자에 virtual을 붙이면 불필요하게 덩치만 커질 수 있습니다.

 

class Base
{
public:
	Base(int posX, int posY);
    
    // 다형성의 의도가 없는 기본 클래의 소멸자는 가상 소멸자로 선언하지 않는다.
    // 현재 이 클래스에서는 아무런 가상 함수가 존재하지 않는다.
    // 만약 소멸자를 가상 소멸자로 선언하면 이에 따른 자료구조(가상함수 포인터)가 
    // 클래스에 포함되어서 쓸데없이 클래스의 덩치만 커지게 된다.
    // virtual ~Base(); // (X)
    ~Base(); // (O)

private:
	int x;
	int y;
};

 

위와 같이 다형성의 목적이 없는 기본 클래스에서 가상 소멸자를 쓰고 객체를 생성하면,

클래스는 가상 함수 테이블(vtbl)이 포함되고, 객체는 가상 함수 포인터(vptr)을 갖게 됩니다.

데이터 멤버(int x, int y )에서 가상 함수 포인터까지 포함되서 덩치만 커집니다.

가상 소멸자는 기본 클래스에 가상 함수가 하나라도 있을 경우에 한하여 정의합니다.