답변:
인터페이스에는 더욱 중요합니다. 클래스의 모든 사용자는 구체적인 구현에 대한 포인터가 아닌 인터페이스에 대한 포인터를 가질 것입니다. 소멸자가 비 가상적 인 경우 삭제하면 파생 클래스의 소멸자가 아닌 인터페이스의 소멸자 (또는 컴파일러가 제공 한 기본값을 지정하지 않은 경우)를 호출합니다. 즉각적인 메모리 누출.
예를 들어
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;
}
[expr.delete]/
: ... if the static type of the object to be deleted is different from its dynamic type, ... the static type shall have a virtual destructor or the behavior is undefined. ...
. Derived가 내재적으로 생성 된 소멸자를 사용한 경우 여전히 정의되지 않습니다.
귀하의 질문에 대한 답변은 종종 있지만 항상 그런 것은 아닙니다. 추상 클래스가 클라이언트가 포인터에 대해 delete를 호출하는 것을 금지하거나 문서에서 그렇게 말하는 경우 가상 소멸자를 자유롭게 선언 할 수 없습니다.
소멸자를 보호하여 클라이언트가 포인터에 대해 delete를 호출하는 것을 금지 할 수 있습니다. 이와 같이 작동하면 가상 소멸자를 생략하는 것이 완벽하고 안전합니다.
결국 가상 메소드 테이블이 없어지고 클라이언트에 대한 포인터를 통해 삭제할 수 없도록하려는 의사를 알리기 때문에 실제로는 가상으로 선언하지 않는 이유가 있습니다.
[이 기사의 항목 4 참조 : http://www.gotw.ca/publications/mill18.htm ]
나는 약간의 연구를하고 당신의 답을 요약하려고 노력했습니다. 다음 질문은 어떤 종류의 소멸자가 필요한지 결정하는 데 도움이됩니다.
이게 도움이 되길 바란다.
* 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 *
입니다.
shared_ptr
객체는 마치 마치 객체를 삭제하려고 시도합니다. 객체로 만든 객체 Base *
의 유형을 기억합니다. 참조 된 링크, 특히 "T는 가상 소멸자가 없거나 비어있는 경우에도 소멸자가 동일한 포인터를 사용하여 delete를 호출하고 원래 유형으로 완료합니다."라는 비트를 참조하십시오.
좋은 습관 일뿐만 아니라 모든 클래스 계층에 대한 규칙 # 1입니다.
왜 그럴까. 전형적인 동물 계층 구조를 취하십시오. 가상 소멸자는 다른 메소드 호출과 마찬가지로 가상 디스패치를 거치게됩니다. 다음 예제를 보자.
Animal* pAnimal = GetAnimal();
delete pAnimal;
동물이 추상 클래스라고 가정하십시오. C ++이 호출 할 적절한 소멸자를 아는 유일한 방법은 가상 메소드 디스패치를 통하는 것입니다. 소멸자가 가상이 아닌 경우 Animal 소멸자를 호출하고 파생 클래스의 객체를 파괴하지 않습니다.
기본 클래스에서 소멸자를 가상으로 만드는 이유는 파생 클래스에서 선택 항목을 단순히 제거하기 때문입니다. 그들의 소멸자는 기본적으로 가상이됩니다.
대답은 간단합니다. 그렇지 않으면 가상이어야합니다. 그렇지 않으면 기본 클래스가 완전한 다형성 클래스가 아닙니다.
Base *ptr = new Derived();
delete ptr; // Here the call order of destructors: first Derived then Base.
위의 삭제를 선호하지만 기본 클래스의 소멸자가 가상이 아닌 경우 기본 클래스의 소멸자 만 호출되고 파생 클래스의 모든 데이터는 삭제되지 않은 상태로 유지됩니다.
delete p
정의되지 않은 동작을 호출합니다. 전화를 걸 수있는 것은 아닙니다Interface::~Interface
.