C++

C++ 순수 가상 소멸자(Pure Virtual Destructor) 총정리

김구티2 2024. 4. 12. 18:51

1. 순수 가상 소멸자의 개념

우리는 C++에서 순수 가상 소멸자를 선언할 수 있다. 소멸자가 순수 가상 객체(클래스의 인스턴스)로 생성된 후, 소멸자의 바디가 제공된다. 이는 소멸자는 파생 클래스에서 재정의되지 않고 역순으로 호출되기 때문이다. 따라서 순수 가상 소멸자의 경우 소멸자 바디를 지정해야 한다.

기본 클래스 포인터 객체를 사용하여 파생 클래스의 인스턴스를 파괴할 때, 가상 소멸자를 사용하여 파생 클래스 객체 또는 인스턴스에 의해 할당된 메모리 공간을 확보한다.

참고로, 오직 '소멸자'만이 가상이 될 수 있다. '생성자'를 가상으로 선언할 수는 없다. 이는 생성자를 Base/Super 클래스에 선언하고, 동일한 기능을 가진 Derived/Sub 클래스에 호출하여 재정의하려고 하면 항상 오류가 발생하기 때문이다. 오버라이딩은 하위 클래스의 상위 클래스에서 메서드를 사용할 수 있는 기능을 의미하므로, 이 기능을 재정의하는 것은 불가능하다. 

 

순수 가상 함수에 함수 바디가 필요한데, 그 이유는 소멸자들이 실제로는 오버라이드 되지 않고, 클래스 파생의 역순으로 항상 호출되기 때문이다. 이것은 파생된 클래스 소멸자가 먼저 호출되고, 그 다음 기본 클래스 소멸자가 호출된다는 것을 의미한다. 만약 순수 가상 소멸자의 정의가 제공되지 않는다면, 객체 소멸 도중 어떤 함수 바디가 호출되겠는가? 그점을 생각해 보면, 컴파일러와 링커는 순수 가상 소멸자를 위한 함수 바디의 존재를 강제한다는 것을 알 수 있다.

 

예시:

// C++ Program to demonstrate a pure virtual destructor
#include <iostream>
using namespace std;

// Initialization of base class
class Base {
public:
     virtual ~Base() = 0;
     // Pure virtual destructor
};

// Initialization of derived class
class Derived : public Base {
public:
     ~Derived() { cout << "~Derived() is executed"; }
};

// Driver Code
int main()
{
     // base class pointer which is
     // allocating fresh storage
     // for Derived function object's
     Base* b = new Derived();
     delete b;
     return 0;
}

 

링커는 위 프로그램에서 다음과 같은 오류를 발생시킨다:

test.cpp:(.text$_ZN7DerivedD1Ev[__ZN7DerivedD1Ev]+0x4c):
undefined reference to `Base::~Base()'  error: ld returned 1 exit status

 

이제 여기서 순수 가상 소멸자에 대한 정의가 제공되면, 프로그램이 컴파일되고 정상적으로 실행될 것이다.

 

예시:

// C++ program to demonstrate if the value of
// of pure virtual destructor are provided
// then the program compiles & runs fine.

#include <iostream>

// Initialization of base class
class Base {
public:
     virtual ~Base() = 0; // Pure virtual destructor
};
Base::~Base() // Explicit destructor call
{
     std::cout << "Pure virtual destructor is called";
}

// Initialization of derived class
class Derived : public Base {
public:
     ~Derived() { std::cout << "~Derived() is executed\n"; }
};

int main()
{
     // Calling of derived member function
     Base* b = new Derived();
     delete b;
     return 0;
}

 

출력:

~Derived() is executed
Pure virtual destructor is called

 

이것은 기본적으로 가상 소멸자에서 값이 전달되는 경우에만 소멸자가 재귀적으로 아래에서 위로 호출되기 때문에 작동하는 것이다. vtable은 클래스가 정의하는 모든 가상 함수의 포인터를 포함하는 테이블이고, 컴파일러는 이상적인 vtable을 가리키는 '숨겨진 포인터'로서 클래스에 vptr을 제공하므로 컴파일러는 컴파일 시 계산된 정확하거나 올바른 인덱스를 vtable에 사용하여 실행 시 올바른 가상 함수를 발송한다.

클래스가 순수 가상 소멸자를 포함할 때 클래스는 추상 클래스(적어도 정의가 없는 함수)가 된다는 점에 유의해야 할 것이다.

 

예시:

// C++ program to demonstrate how a class becomes
// an abstract class when a pure virtual destructor is
// passed

#include <iostream>
class Test {
public:
     virtual ~Test() = 0;
     // Test now becomes abstract class
};
Test::~Test() {}

// Driver Code
int main()
{
     Test p;
     Test* t1 = new Test;
     return 0;
}

 

위 프로그램은 컴파일에 실패하고 다음 오류 메시지를 표시한다:

[Error] cannot declare variable 'p' to be of abstract type 'Test' 
[Note] because the following virtual functions are pure within 'Test': 
[Note] virtual Test::~Test() 
[Error] cannot allocate an object of abstract type 'Test' 
[Note] since type 'Test' has pure virtual functions

 

728x90