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

클래스에 대해 가상 소멸자를 선언 하지 않는 합당한 이유가 있습니까? 구체적으로 작성을 피해야 할 때는 언제입니까?



답변

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

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

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


답변

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

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
}


답변

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


답변

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

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

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

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


답변

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

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

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


답변

모든 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의 스택에 앉아 있다는 것입니다. 이 클래스의 하위 클래스는 말할 것도없고이 클래스의 개체에 대한 포인터를 전달하는 경우 잘못된 작업을하고있는 것입니다.


답변

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

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

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

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