언제 가상 소멸자를 사용하지 말아야합니까?


답변:


72

아래에 해당하는 경우 가상 소멸자를 사용할 필요가 없습니다.

  • 클래스를 파생시킬 의도가 없습니다.
  • 힙에서 인스턴스화하지 않음
  • 슈퍼 클래스의 포인터에 저장할 의도가 없습니다.

당신이 정말로 기억에 눌리지 않는 한 그것을 피할 특별한 이유가 없습니다.


25
이것은 좋은 대답이 아닙니다. "필요 없음"은 "해서는 안된다"와 다르며 "의도 없음"은 "불가능하게 만들다"와 다릅니다.
Windows 프로그래머

5
또한 추가 : 기본 클래스 포인터를 통해 인스턴스를 삭제할 의도가 없습니다.
Adam Rosenfield

9
이것은 실제로 질문에 대한 답이 아닙니다. 가상 dtor를 사용하지 않는 이유는 무엇입니까?
mxcl

9
할 필요가 없을 때는하지 않는 것이 좋은 이유라고 생각합니다. XP의 단순한 디자인 원칙을 따릅니다.
1

12
당신이 "의도 없음"이라고 말함으로써 당신은 당신의 수업이 어떻게 사용 될지에 대해 엄청난 가정을하고있는 것입니다. 대부분의 경우 (따라서 기본값이어야 함) 가장 간단한 해결책은 가상 소멸자를 갖는 것이어야하며 특별한 이유가없는 경우에만 피해야합니다. 그래서 나는 여전히 좋은 이유가 무엇인지 궁금합니다.
ckarras

68

질문에 명시 적으로 대답하기 위해, 즉 언제 가상 소멸자를 선언 하지 않아야합니다 .

C ++ '98 / '03

가상 소멸자를 추가하면 클래스가 POD (일반 이전 데이터) * 또는 집계에서 비 POD로 변경 될 수 있습니다 . 클래스 유형이 어딘가에서 초기화 된 경우 프로젝트 컴파일이 중지 될 수 있습니다.

struct A {
  // virtual ~A ();
  int i;
  int j;
};
void foo () { 
  A a = { 0, 1 };  // Will fail if virtual dtor declared
}

극단적 인 경우 이러한 변경으로 인해 클래스가 POD가 필요한 방식으로 사용되는 정의되지 않은 동작 (예 : 생략 매개 변수를 통해 전달 또는 memcpy와 함께 사용)이 발생할 수도 있습니다.

void bar (...);
void foo (A & a) { 
  bar (a);  // Undefined behavior if virtual dtor declared
}

[* POD 유형은 메모리 레이아웃에 대해 특정 보증이있는 유형입니다. 표준은 실제로 POD 유형의 객체에서 문자 배열 (또는 부호없는 문자)로 복사하고 다시 되 돌리는 경우 결과는 원래 객체와 동일하다고 말합니다.]

최신 C ++

최신 버전의 C ++에서 POD의 개념은 클래스 레이아웃과 구성, 복사 및 파괴로 나뉘어졌습니다.

줄임표의 경우 더 이상 정의되지 않은 동작이 아니며 이제 구현 정의 의미 체계로 조건부 지원됩니다 (N3937-~ C ++ '14-5.2.2 / 7).

... 사소하지 않은 복사 생성자, 사소하지 않은 이동 생성자 또는 해당 매개 변수가없는 사소한 소멸자를 갖는 클래스 유형 (Clause 9)의 잠재적으로 평가 된 인수를 전달하는 것은 구현시 조건부로 지원됩니다. 정의 된 의미론.

=defaultwill 이외의 소멸자를 선언하면 사소하지 않습니다 (12.4 / 5).

... 소멸자는 사용자가 제공하지 않으면 사소합니다 ...

Modern C ++에 대한 다른 변경 사항은 생성자를 추가 할 수 있으므로 집계 초기화 문제의 영향을 줄입니다.

struct A {
  A(int i, int j);
  virtual ~A ();
  int i;

  int j;
};
void foo () { 
  A a = { 0, 1 };  // OK
}

1
당신 말이 맞고 내가 틀렸어요. 성능이 유일한 이유는 아닙니다. 그러나 이것은 내가 나머지 부분에 대해 옳았다는 것을 보여줍니다. 클래스의 프로그래머는 다른 사람이 클래스를 상속하지 못하도록 코드를 포함하는 것이 더 좋습니다.
Windows 프로그래머

리차드에게, 당신이 쓴 것에 대해 조금 더 언급 해 주시겠습니까? 나는 당신의 요점을 이해하지 못하지만 인터넷 검색을 통해 찾은 유일한 귀중한 점인 것 같습니다.) 아니면 더 자세한 설명에 대한 링크를 줄 수 있습니까?
John Smith

1
@JohnSmith 답변을 업데이트했습니다. 도움이 되었기를 바랍니다.
Richard Corden

28

가상 메서드가있는 경우에만 가상 소멸자를 선언합니다. 일단 가상 메서드가 있으면 힙에서 인스턴스화하거나 기본 클래스에 대한 포인터를 저장하는 것을 피할 수 있다고 믿지 않습니다. 둘 다 매우 일반적인 작업이며 소멸자가 가상으로 선언되지 않은 경우 자동으로 리소스가 누출되는 경우가 많습니다.


3
그리고 실제로 gcc에 대한 경고 옵션이 있습니다.이 경우 정확히 해당 사례에 대해 경고합니다 (가상 방법이지만 가상 dtor 없음).
CesarB

6
그런 다음 다른 가상 기능이 있는지 여부에 관계없이 클래스에서 파생되면 메모리 누수의 위험을 감수하지 않습니까?
Mag Roader

1
나는 매기에 동의합니다. 가상 소멸자 및 / 또는 가상 방법의 사용은 별도의 요구 사항입니다. 가상 소멸자는 클래스가 정리 (예 : 메모리 삭제, 파일 닫기 등)를 수행 할 수있는 기능을 제공하고 모든 멤버의 생성자가 호출되도록합니다.
user48956

7

가상 소멸자는 delete클래스 유형을 사용하여 하위 클래스의 개체에 대한 포인터에서 호출 될 수 있는 기회가 있을 때마다 필요 합니다. 이렇게하면 컴파일러가 컴파일 타임에 힙에있는 개체의 클래스를 알 필요없이 런타임에 올바른 소멸자가 호출되도록합니다. 예를 들어 다음과 B같은 하위 클래스 라고 가정합니다 A.

A *x = new B;
delete x;     // ~B() called, even though x has type A*

코드가 성능에 중요하지 않은 경우 안전을 위해 작성하는 모든 기본 클래스에 가상 소멸자를 추가하는 것이 합리적입니다.

그러나 delete타이트한 루프에서 많은 객체 를 발견 한 경우 가상 함수 (비어있는 함수도 포함) 호출의 성능 오버 헤드가 눈에 띄게 나타날 수 있습니다. 컴파일러는 일반적으로 이러한 호출을 인라인 할 수 없으며 프로세서는 어디로 가야할지 예측하는 데 어려움을 겪을 수 있습니다. 이것이 성능에 큰 영향을 미칠 것 같지는 않지만 언급 할 가치가 있습니다.


"코드가 성능에 중요하지 않다면 안전을 위해 작성하는 모든 기본 클래스에 가상 소멸자를 추가하는 것이 합리적입니다." 더 내가 볼 때마다 대답 강조되어야한다
csguy

5

가상 함수는 할당 된 모든 객체가 가상 함수 테이블 포인터에 의해 메모리 비용이 증가 함을 의미합니다.

따라서 프로그램이 매우 많은 수의 일부 개체를 할당하는 경우 개체 당 추가 32 비트를 저장하기 위해 모든 가상 기능을 피하는 것이 좋습니다.

다른 모든 경우에, 당신은 dtor를 가상으로 만들기 위해 자신을 디버그 할 수 있습니다.


1
nit-picking에 불과하지만 요즘에는 포인터가 32 비트가 아닌 64 비트가되는 경우가 많습니다.
Head Geek

5

모든 C ++ 클래스가 동적 다형성이있는 기본 클래스로 사용하기에 적합한 것은 아닙니다.

클래스가 동적 다형성에 적합하도록하려면 해당 소멸자가 가상이어야합니다. 또한 서브 클래스가 재정의 할 수있는 모든 메서드 (모든 공용 메서드와 내부적으로 사용되는 일부 보호 된 메서드를 의미 할 수 있음)는 가상이어야합니다.

클래스가 동적 다형성에 적합하지 않은 경우 소멸자를 가상으로 표시해서는 안됩니다. 그렇게하는 것은 오해의 소지가 있기 때문입니다. 사람들이 수업을 잘못 사용하도록 권장합니다.

다음은 소멸자가 가상 ​​인 경우에도 동적 다형성에 적합하지 않은 클래스의 예입니다.

class MutexLock {
    mutex *mtx_;
public:
    explicit MutexLock(mutex *mtx) : mtx_(mtx) { mtx_->lock(); }
    ~MutexLock() { mtx_->unlock(); }
private:
    MutexLock(const MutexLock &rhs);
    MutexLock &operator=(const MutexLock &rhs);
};

이 수업의 요점은 RAII의 스택에 앉아 있다는 것입니다. 이 클래스의 하위 클래스는 말할 것도없고이 클래스의 개체에 대한 포인터를 전달하는 경우 잘못된 작업을하고있는 것입니다.


2
다형성 사용은 다형성 삭제를 의미하지 않습니다. 클래스에 가상 메서드가 있지만 가상 소멸자가없는 사용 사례가 많이 있습니다. 거의 모든 GUI 툴킷에서 일반적인 정적으로 정의 된 대화 상자를 고려하십시오. 부모 창은 자식 개체를 파괴하고 각 개체의 정확한 유형을 알고 있지만 모든 자식 창은 텍스트에 대한 텍스트를 가져 오는 액세스 가능성 API, 적중 테스트, 그리기와 같은 여러 위치에서 다형성으로 사용됩니다. 음성 엔진 등
Ben Voigt

4
사실이지만 질문자는 가상 소멸자를 구체적으로 피해야 할 때를 묻습니다. 설명하는 대화 상자에서 가상 소멸자는 무의미하지만 IMO는 해롭지 않습니다. 기본 클래스 포인터를 사용하여 대화 상자를 삭제할 필요가 없다고 확신 할 수 없습니다. 따라서 가상 소멸자 를 피하는 것은 문제 가 아닙니다. 파생에 적합하지 않은 클래스의 가상 소멸자 오해의 소지가 있기 때문에 해 롭습니다.
Steve Jessop 2010-04-13

4

소멸자를 가상으로 선언하지 않는 좋은 이유는 이것이 가상 함수 테이블이 추가되지 않도록 클래스를 저장하는 경우이며 가능한 한 피해야합니다.

나는 많은 사람들이 안전한 편에 있기 위해 항상 소멸자를 가상으로 선언하는 것을 선호한다는 것을 알고 있습니다. 그러나 클래스에 다른 가상 기능이 없다면 가상 소멸자를 갖는 것은 정말로 의미가 없습니다. 클래스를 다른 클래스에서 파생시킨 다른 사람들에게 주더라도 클래스에 업 캐스트 된 포인터에 대해 delete를 호출 할 이유가 없습니다. 그렇게한다면 버그로 간주 할 것입니다.

좋아요, 하나의 예외가 있습니다. 즉, 클래스가 파생 객체의 다형성 삭제를 수행하는 데 (잘못) 사용 된 경우, 당신이나 다른 사람들은 이것이 가상 소멸자가 필요하다는 것을 알고 있기를 바랍니다.

다시 말해, 클래스에 비가 상 소멸자가있는 경우 이것은 매우 명확한 진술입니다. "파생 객체를 삭제하는 데 나를 사용하지 마십시오!"


3

많은 수의 인스턴스가있는 매우 작은 클래스가있는 경우 vtable 포인터의 오버 헤드로 인해 프로그램의 메모리 사용량이 달라질 수 있습니다. 클래스에 다른 가상 메서드가없는 한 소멸자를 비가 상으로 만들면 오버 헤드가 절약됩니다.


1

일반적으로 소멸자를 가상으로 선언하지만 내부 루프에서 사용되는 성능에 중요한 코드가있는 경우 가상 테이블 조회를 피하는 것이 좋습니다. 충돌 검사와 같은 경우에 중요 할 수 있습니다. 그러나 상속을 사용하는 경우 이러한 개체를 어떻게 파괴하는지주의하십시오. 그렇지 않으면 개체의 절반 만 파괴 할 것입니다.

만약 가상 테이블을 조회하는 오브젝트 발생주의 모든 객체에있어서 가상이다. 따라서 클래스에 다른 가상 메서드가있는 경우 소멸자에서 가상 사양을 제거 할 필요가 없습니다.


1

클래스에 vtable이 없는지 절대적으로 확실하게 확인해야한다면 가상 소멸자도 없어야합니다.

이것은 드문 경우이지만 발생합니다.

이를 수행하는 가장 익숙한 패턴의 예는 DirectX D3DVECTOR 및 D3DMATRIX 클래스입니다. 이들은 구문 설탕에 대한 함수 대신 클래스 메서드이지만 이러한 클래스는 많은 고성능 응용 프로그램의 내부 루프에서 특별히 사용되기 때문에 함수 오버 헤드를 피하기 위해 의도적으로 vtable이 없습니다.


0

기본 클래스에서 수행되고 가상으로 작동해야하는 작업은 가상이어야합니다. 삭제가 기본 클래스 인터페이스를 통해 다형 적으로 수행 될 수있는 경우 가상으로 작동하고 가상이어야합니다.

클래스에서 파생하지 않으려는 경우 소멸자는 가상 일 필요가 없습니다. 그리고 그렇게하더라도 보호 된 비가 상 소멸자는 기본 클래스 포인터의 삭제가 필요하지 않은 경우와 마찬가지로 좋습니다 .


-7

성능에 대한 답변은 내가 아는 유일한 답변입니다. 소멸자를 비 가상화하면 속도가 정말 빨라진다는 것을 측정하고 확인한 경우 해당 클래스에 속도 향상이 필요한 다른 항목도있을 수 있지만이 시점에서 더 중요한 고려 사항이 있습니다. 언젠가 누군가는 당신의 코드가 그들에게 좋은 기본 클래스를 제공하고 일주일의 작업을 절약 할 수 있다는 것을 발견 할 것입니다. 코드를 기본으로 사용하는 대신 해당주의 작업을 수행하고 코드를 복사하여 붙여 넣는 것이 좋습니다. 아무도 당신에게서 상속받을 수 없도록 중요한 방법 중 일부를 비공개로 만드는 것이 좋습니다.


다형성은 확실히 일을 느리게 할 것입니다. 다형성이 필요한 상황과 비교하면 더 느릴 것입니다. 예 : RTTI와 switch 문을 사용하여 리소스를 정리하여 기본 클래스 소멸자에서 모든 논리를 구현합니다.
9월

1
C ++에서 문서화 한 클래스가 기본 클래스로 사용하기에 적합하지 않은 클래스에서 상속받는 것을 막는 것은 사용자의 책임이 아닙니다. 상속을 신중하게 사용하는 것은 내 책임입니다. 물론 집 스타일 가이드가 달리 말하지 않는 한.
Steve Jessop

1
... 소멸자를 가상으로 만든다고해서 클래스가 반드시 기본 클래스로 올바르게 작동하는 것은 아닙니다. 그래서 그것을 가상 "단지"로 표시하는 것은 평가를하는 대신 내 코드로 현금화 할 수없는 수표를 작성하는 것입니다.
Steve Jessop
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.