항목 7의 내용은 c++을 공부 했다면 당연히 알고 있어야 하는 내용이지만 몇몇 중요한 내용도 추가된것 같아서 남긴다. 다형성을 가진 기본 클래스에서는 소멸자를 가상소멸자로 선언하자는 것인데 하나의 클래스를 상속시켜서 파생클래스를 만든다고 했을때 반드시 상속의 시초가되는 기본 클래스의 소멸자는 가상함수로 선언을 하라는 말이다. 이유는 가상 소멸자로 선언을 하지 않으면 파생된 최종 클래스의 소멸자만 호출이 되고 그 부모 객체의 소멸자가 호출되지 않기 때문이다. 이 내용은 앞서 말했다시피 C++을 공부했다면 다들 알고 있을 것이다. 자 그런데 여기서 한 가지 놓쳤을만한 이야기를 해볼까 한다. 기본적으로 비어있는 클래스는 1바이트이다.
class Parent
{
public:
Parent() = default;
~parent() {}
};
int main()
{
std::cout << sizeof(Parent) << std::endl; // 1
}
그런데 만약 우리가 클래스에 자료형을 추가하게 된다면 그때 부터는 그 자료형의 바이트 수 만큼이 클래스의 크기가 된다.
class Parent
{
public:
Parent() = default;
~parent() {}
private:
int a; // 4
double b; // 8
char* name; // 32bit면 4, 64bit면 8
};
int main()
{
std::cout << sizeof(Parent) << std::endl; // 4 + 8 + 8 = 20
}
자 그렇다면 이 클래스에 가상 함수를 선언하게 되면 무슨일이 발생할까?
class Parent
{
public:
Parent() = default;
virtual ~parent() {}
};
int main()
{
std::cout << sizeof(Parent) << std::endl; // 32bit 4, 64bit 8
}
자 위와 같이 다형성을 위해 기본 클래스를 가상 소멸자를 이용해 선언하였다. 그런데 똑같이 비어있는 클래스인데 32비트 환경이면 4바이트 64비트 환경이면 8바이트 이다. 왜 그럴까? 이유는 가상함수의 구조에 대해 이해하면 알게된다. 가상함수를 선언하게 되면 클래스는 내부적으로 vptr이라는 녀석을 생성하게 된다. 이것은 가상함수 테이블을 가리키는 포인터이다. 가상 함수 테이블에는 우리가 선언한 가상 함수에 대한 정보가 들어가 있다고 생각하면 된다. 자, 그렇다. 이유는 가상함수를 저장하기 위해 생성한 vptr이라는 녀석 때문이다. vptr은 포인터이기 때문인데 포인터 자료형은 주소를 저장하기 위한 객체로 빌드 환경(32비트, 64비트)에 따라 크기가 각각 4바이트, 8바이트로 바뀐다는 것을 알것이다. 이러한 이유 때문에 클래스의 크기가 변화하게 된다.
자, 다음으로 한 가지 주의해야 할 내용은 우리가 라이브러리를 만들다가 문자열이나 STL 자료형을 좀더 확장하기 위해 std::string 이나 std::vector등과 같은 클래스를 상속 받아서 설계 해야지 할 수도 있다. 그러나 이것은 상당히 문제가 될 수 있다. std::string이나 std::vector와 같은 STL자료형들은 모두 가상 소멸자가 존재하지 않는다. 그래서 그렇게 해버리면 부모 클래스의 소멸자가 호출이 되지 않아 메모리 누수가 발생할 수도 있으니 그러지 말도록 하자.
마지막으로 한가지 더 추가하자면 파생클래스를 다루기 쉽게 하기 위해 안에 아무런 내용물이 없는 기본 클래스를 만든다고 치자. 그리고 이 클래스는 오로지 파생용으로만 쓰지 단독으로는 생성하지 못하게 막고 싶다. 그럴 경우에는 클래스안에 순수 가상함수를 만들면 추상 클래스가 되어 단독으로 생성 할 수 없게 된다. 하지만 나는 여기서 순수 가상함수로 마땅히 쓸 내용이 없다. 이런 경우에는 어떻게 해야 할까? 방법은 바로 순수 가상 소멸자를 선언하는 것이다.
class Parent
{
public:
virtual ~parent() = 0;
};
class Child : public Parent
{
public:
...
};
int main()
{
Parent a; // 오류 발생
}
이런식으로 단순히 다형성만을 보장하는 추상클래스를 선언할 수 있다는 점도 알아두자.
'C++ > Effective C++' 카테고리의 다른 글
항목 9 객체 생성 및 소멸 과정 중에는 절대로 가상 함수를 호출 하지 말자 (0) | 2024.01.18 |
---|---|
항목 8 예외가 소멸자를 떠나지 못하도록 붙들어 놓자 (0) | 2024.01.18 |
항목 6 컴파일러가 만들어낸 함수가 필요 없으면 확실히 이들의 사용을 금해 버리자 (2) | 2024.01.18 |
항목 4 객체를 사용하기 전에 반드시 그 객체를 초기화 하자 (0) | 2024.01.18 |
항목 3 낌새만 보이면 const를 들이대 보자! (0) | 2024.01.17 |