[c++] C ++에서 추상 클래스에 대한 가상 소멸자를 선언해야하는 이유는 무엇입니까?

C ++에서 기본 클래스에 대한 가상 소멸자를 선언하는 것이 좋지만 virtual인터페이스로 작동하는 추상 클래스에 대해서도 소멸자 를 선언하는 것이 항상 중요 합니까? 이유와 예를 알려주세요.



답변

인터페이스에는 더욱 중요합니다. 클래스의 모든 사용자는 구체적인 구현에 대한 포인터가 아닌 인터페이스에 대한 포인터를 가질 것입니다. 소멸자가 비 가상적 인 경우 삭제하면 파생 클래스의 소멸자가 아닌 인터페이스의 소멸자 (또는 컴파일러가 제공 한 기본값을 지정하지 않은 경우)를 호출합니다. 즉각적인 메모리 누출.

예를 들어

class Interface
{
   virtual void doSomething() = 0;
};

class Derived : public Interface
{
   Derived();
   ~Derived()
   {
      // Do some important cleanup...
   }
};

void myFunc(void)
{
   Interface* p = new Derived();
   // The behaviour of the next line is undefined. It probably 
   // calls Interface::~Interface, not Derived::~Derived
   delete p;
}


답변

귀하의 질문에 대한 답변은 종종 있지만 항상 그런 것은 아닙니다. 추상 클래스가 클라이언트가 포인터에 대해 delete를 호출하는 것을 금지하거나 문서에서 그렇게 말하는 경우 가상 소멸자를 자유롭게 선언 할 수 없습니다.

소멸자를 보호하여 클라이언트가 포인터에 대해 delete를 호출하는 것을 금지 할 수 있습니다. 이와 같이 작동하면 가상 소멸자를 생략하는 것이 완벽하고 안전합니다.

결국 가상 메소드 테이블이 없어지고 클라이언트에 대한 포인터를 통해 삭제할 수 없도록하려는 의사를 알리기 때문에 실제로는 가상으로 선언하지 않는 이유가 있습니다.

[이 기사의 항목 4 참조 : http://www.gotw.ca/publications/mill18.htm ]


답변

나는 약간의 연구를하고 당신의 답을 요약하려고 노력했습니다. 다음 질문은 어떤 종류의 소멸자가 필요한지 결정하는 데 도움이됩니다.

  1. 수업이 기본 수업으로 사용됩니까?
    • 번호 : 클래스의 각 개체에 피하기 V-포인터 공공 비 가상 소멸자를 선언 * .
    • 예 : 다음 질문을 읽으십시오.
  2. 당신의 기본 수업은 추상적입니까? (즉, 가상의 순수한 방법?)
    • 아니요 : 클래스 계층을 다시 디자인하여 기본 클래스를 추상화하십시오.
    • 예 : 다음 질문을 읽으십시오.
  3. 기본 포인터를 통해 다형성 삭제를 허용 하시겠습니까?
    • 아니요 : 원하지 않는 사용을 방지하기 위해 보호 된 가상 소멸자를 선언하십시오.
    • 예 : 퍼블릭 가상 소멸자를 선언합니다 (이 경우 오버 헤드 없음).

이게 도움이 되길 바란다.

* C ++에는 클래스를 최종 클래스로 표시 할 수있는 방법이 없다는 점에 유의해야합니다 (즉, 하위 클래스를 지정할 수 없음) 소멸자를 비가 상 및 공개로 선언하기로 결정한 경우 동료 프로그래머에게 수업에서 파생됩니다.

참고 문헌 :


답변

예, 항상 중요합니다. 파생 클래스는 메모리가 할당되거나 객체가 파괴 될 때 정리해야하는 다른 리소스에 대한 참조를 보유 할 수 있습니다. 인터페이스 / 추상 클래스에 가상 소멸자를 제공하지 않으면 기본 클래스 핸들을 통해 파생 클래스 인스턴스를 삭제할 때마다 파생 클래스의 소멸자가 호출되지 않습니다.

따라서 메모리 누수 가능성을 열어 가고 있습니다.

class IFoo
{
  public:
    virtual void DoFoo() = 0;
};

class Bar : public IFoo
{
  char* dooby = NULL;
  public:
    virtual void DoFoo() { dooby = new char[10]; }
    void ~Bar() { delete [] dooby; }
};

IFoo* baz = new Bar();
baz->DoFoo();
delete baz; // memory leak - dooby isn't deleted


답변

항상 필요한 것은 아니지만 좋은 습관임을 알 수 있습니다. 기본 유형의 포인터를 통해 파생 객체를 안전하게 삭제할 수 있습니다.

예를 들어 :

Base *p = new Derived;
// use p as you see fit
delete p;

Base가상 소멸자가없는 경우 객체가 마치 것처럼 삭제하려고 시도하기 때문에 잘못된 형식 Base *입니다.


답변

좋은 습관 일뿐만 아니라 모든 클래스 계층에 대한 규칙 # 1입니다.

  1. C ++에서 계층의 기본 클래스에는 가상 소멸자가 있어야합니다.

왜 그럴까. 전형적인 동물 계층 구조를 취하십시오. 가상 소멸자는 다른 메소드 호출과 마찬가지로 가상 디스패치를 ​​거치게됩니다. 다음 예제를 보자.

Animal* pAnimal = GetAnimal();
delete pAnimal;

동물이 추상 클래스라고 가정하십시오. C ++이 호출 할 적절한 소멸자를 아는 유일한 방법은 가상 메소드 디스패치를 ​​통하는 것입니다. 소멸자가 가상이 아닌 경우 Animal 소멸자를 호출하고 파생 클래스의 객체를 파괴하지 않습니다.

기본 클래스에서 소멸자를 가상으로 만드는 이유는 파생 클래스에서 선택 항목을 단순히 제거하기 때문입니다. 그들의 소멸자는 기본적으로 가상이됩니다.


답변

대답은 간단합니다. 그렇지 않으면 가상이어야합니다. 그렇지 않으면 기본 클래스가 완전한 다형성 클래스가 아닙니다.

    Base *ptr = new Derived();
    delete ptr; // Here the call order of destructors: first Derived then Base.

위의 삭제를 선호하지만 기본 클래스의 소멸자가 가상이 아닌 경우 기본 클래스의 소멸자 만 호출되고 파생 클래스의 모든 데이터는 삭제되지 않은 상태로 유지됩니다.