C++ 순수 가상 소멸자

Abdul Mateen 2023년10월12일
  1. 기본 클래스 포인터를 사용하여 파생 클래스 함수 호출
  2. 순수 가상 함수 및 추상 클래스
  3. 가상 및 순수 가상 소멸자
  4. 순수 가상 소멸자
C++ 순수 가상 소멸자

이 자습서에서는 C++의 순수 가상 소멸자에 대해 설명합니다. 그러나 개념을 완전히 이해하려면 서로 다른 관련 개념을 이해해야 합니다.

먼저 기본 클래스 포인터를 사용하여 파생 클래스 함수를 호출하는 방법을 살펴본 다음 상속에서 소멸자 호출 문제를 논의합니다. 마지막으로 순수 가상 소멸자와 구현 문제에 대해 논의합니다.

기본 클래스 포인터를 사용하여 파생 클래스 함수 호출

기본 클래스 포인터는 파생 클래스 개체를 가리킬 수 있습니다. 이렇게 하면 서로 다른 파생 클래스 개체를 처리하기 위해 여러 파생 클래스 포인터를 선언하고 관리하지 않아도 됩니다.

그러나 기본 클래스 포인터는 일반적으로 파생 클래스 메서드를 호출할 수 없습니다. 예를 들어 다음 코드를 참조하세요.

#include <iostream>
using namespace std;
class P {};
class C : public P {
 public:
  void f() { cout << "Function C\n"; }
};
int main() {
  P *ptr = new C;
  ptr->f();
  return 0;
}

컴파일 시 이 코드는 다음 오류를 표시합니다.

virtual_destructor0.cpp: In function ‘int main()’:
virtual_destructor0.cpp:12:7: error: ‘class P’ has no member named ‘f’
  ptr->f();

기본 클래스에서 동일한 함수를 선언하면 컴파일러는 파생 클래스 함수 대신 기본 클래스 함수를 호출합니다. 클래스 P에 함수 f를 추가한 다음 코드를 참조하십시오.

#include <iostream>
using namespace std;
class P {
 public:
  void f() { cout << "Function P\n"; }
};
class C : public P {
 public:
  void f() { cout << "Function C\n"; }
};
int main() {
  P *ptr = new C;
  ptr->f();
  return 0;
}

함수 f를 추가한 후 출력을 참조하십시오.

Function P

이는 기본 클래스 포인터가 파생 클래스 함수가 아니라 기본 클래스 함수를 호출함을 의미합니다. 우리는 클래스 포인터가 포함하는 개체의 클래스가 아니라 해당 클래스에서 함수를 호출할 수 있다는 규칙을 추론합니다.

그러나 Virtual Function이라는 또 다른 중요한 개념이 있습니다. 가상 함수를 사용하면 파생 클래스가 기본 클래스 포인터가 호출할 수 있는 기본 클래스 함수를 재정의할 수 있습니다.

함수 재정의는 파생 클래스가 해당 기본 클래스 함수에 대해 고유한 기능을 제공함을 의미합니다. 기본 클래스는 파생 클래스에서 재정의를 허용하는 함수와 함께 virtual 키워드를 작성합니다.

다시 코드를 참조하십시오.

#include <iostream>
using namespace std;
class P {
 public:
  virtual void f() { cout << "Function P\n"; }
};
class C : public P {
 public:
  void f() { cout << "Function C\n"; }
};
int main() {
  P *ptr = new C;
  ptr->f();
  return 0;
}

기본 클래스 함수 f() 앞에 virtual 키워드가 추가된 점에 유의하십시오. 새로 추가한 후 출력을 참조하십시오.

Function C

이는 기본 클래스 포인터가 이제 이 기본 포인터가 참조하는 객체 유형에 따라 함수를 호출하고 있음을 의미합니다(즉, 파생 클래스의 멤버가 실행됨). 이전 동작은 포인터 유형(즉, 기본 클래스)의 멤버를 호출하는 것이었습니다.

그러나 가상 클래스 기능을 제공하는 것은 파생 클래스 구현자의 선택이며, 이는 파생 클래스가 가상 기본 기능을 재정의하거나 재정의하지 않을 수 있음을 의미합니다.

경우에 따라 클래스의 설계자(디자이너)는 추상 기본 클래스(ABC라고도 함) 및 순수 가상 함수와 같은 더 많은 개념을 포함하는 기본 클래스 기능을 구현하기 위해 파생 클래스를 적용합니다.

신속하게 논의하겠습니다. 필요한 경우 참조된 웹사이트를 방문할 수 있습니다.

순수 가상 함수 및 추상 클래스

기본 클래스는 순수 가상 함수를 선언하여 파생 클래스에서 가상 기본 메서드 구현을 적용할 수 있습니다. 순수 가상 함수에는 구현이 없습니다.

또한 순수 가상 함수가 하나 이상 있는 클래스는 추상 클래스가 됩니다. 추상 클래스는 인스턴스화할 수 없습니다(그러나 파생 클래스 개체를 가리키는 포인터를 선언할 수 있음).

순수 가상 함수의 구문은 약간 다릅니다.

virtual void f() = 0;

순수 가상 함수에는 정의나 본문이 없습니다. 따라서 호출할 수 없습니다. 그러나 순수 가상 함수를 구현하기 위해 파생 클래스를 적용합니다.

그렇지 않으면 파생 클래스는 추상(즉, 인스턴스화 불가능)이 됩니다.

반대로 클래스를 추상화하려면 하나의 추상 함수만 만들면 됩니다. 기사 뒷부분에서 이 문장을 언급하겠습니다.

가상 및 순수 가상 소멸자

우선, 소멸자는 재정의할 수 없습니다. 그러나 기본 및 자식 클래스 소멸자를 모두 호출하려면 포인터(기본) 클래스의 소멸자가 가상이어야 합니다.

그렇지 않으면 자식 클래스 소멸자만 호출됩니다. 예를 들어 다음 코드를 살펴보겠습니다.

#include <iostream>
using namespace std;
class P {
 public:
  ~P() { cout << "P Class Destructor\n"; }
};
class C : public P {
 public:
  ~C() { cout << "C Class Destructor\n"; }
};
int main() {
  P *ptr = new C;
  delete ptr;
  return 0;
}

출력:

P Class Destructor

여기에서 포인터가 P 클래스이고 객체가 C 클래스임을 알 수 있습니다. 삭제 작업은 기본 클래스 소멸자만 호출하므로 파생 클래스의 리소스를 삭제해야 하는 경우에는 삭제할 수 없습니다.

이를 위해 기본 클래스에서 가상 소멸자를 선언해야 합니다. 이미 알고 있듯이 이를 위해 기본 클래스 소멸자와 함께 virtual 키워드를 추가해야 합니다.

샘플 코드는 다음과 같습니다.

#include <iostream>
using namespace std;
class P {
 public:
  virtual ~P() { cout << "P Class Destructor\n"; }
};
class C : public P {
 public:
  ~C() { cout << "C Class Destructor\n"; }
};
int main() {
  P *ptr = new C;
  delete ptr;
  return 0;
}

출력:

C Class Destructor
P Class Destructor

순수 가상 소멸자

마지막으로 본론으로 들어가 순수 가상 소멸자를 통해 부모 클래스와 파생 클래스 모두의 소멸자를 호출하는 문제를 해결해 보겠습니다.

앞서 언급했듯이 소멸자는 다른 함수와 다르며 소멸자를 재정의한다는 개념이 없습니다. 따라서 순수 가상 소멸자는 파생 클래스가 소멸자를 구현하도록 강제할 수 없습니다.

이제 다른 각도에서 사물을 살펴보겠습니다. 클래스를 추상화하려면 최소한 하나의 순수 가상 함수를 선언해야 합니다.

이러한 경우 파생 클래스는 구체적인 클래스가 되기 위해 순수 가상 함수를 구현하도록 바인딩됩니다. 따라서 이 클래스에서 순수한 가상 소멸자를 선언하여 클래스 추상화를 만듭니다. 그러면 파생 클래스가 어떤 것도 구현하도록 강제하지 않습니다.

순수한 가상 소멸자의 확률

항상 공짜 점심 없음 정리를 기억하십시오. 순수한 가상 소멸자를 선언하는 데는 약간의 문제가 있습니다.

다음 예제를 통해 이를 살펴보겠습니다.

class P {
 public:
  virtual ~P() = 0;
};

이 코드를 컴파일하면 다음 오류가 발생합니다.

/tmp/ccZfsvAh.o: In function `C::~C()':
virtual_destructor4.cpp:(.text._ZN1CD2Ev[_ZN1CD5Ev]+0x22): undefined reference to `P::~P()'
collect2: error: ld returned 1 exit status

다행히도 세상의 대부분의 복잡한 문제에는 간단한 해결책이 있습니다. 이 오류는 컴파일러가 사용할 수 없는 소멸자의 정의를 찾고 있기 때문에 발생합니다.

따라서 솔루션은 순수 가상 소멸자를 정의하는 것입니다. 모순처럼 보입니다. 순수한 가상 함수의 정의를 요구하는 컴파일러?

예, 이것은 C++의 다른 확률 중 일부입니다. 클래스 내에서 순수 가상 소멸자를 구현할 수 없습니다. 그러나 클래스 외부에서 구현할 수 있습니다.

다음 코드를 살펴보십시오.

class P {
 public:
  virtual ~P() = 0;
};
P::~P() { cout << "P Class Destructor\n"; }

마지막 줄에서 클래스 외부에 순수 가상 소멸자를 정의했으며 컴파일러 오류가 없습니다. 또한 기본 클래스와 파생 클래스 소멸자를 모두 호출할 수 있습니다.

C Class Destructor
P Class Destructor

결론적으로 추상 기본 클래스를 만들기 위해 그 안에 순수 가상 소멸자를 선언할 수 있습니다. 그러나 클래스 외부에서 이 순수 가상 소멸자를 정의해야 합니다(모든 리소스/메모리를 정리할 수 있음). 이렇게 하면 기본 클래스와 파생 클래스 소멸자를 모두 호출할 수 있습니다.

관련 문장 - C++ Destructor