가상 소멸자를 언제 사용해야합니까?


1486

나는 대부분의 OO 이론을 잘 이해하고 있지만 나를 혼란스럽게 만드는 것은 가상 소멸자입니다.

체인의 모든 객체에 대해 소멸자가 항상 호출된다고 생각했습니다.

언제 가상으로 만들어야합니까?


6
이 참조 : 가상 소멸자
나빈

146
모든 소멸자는 다운 됩니다. virtual중간 대신 맨 위에서 시작해야합니다.
Mooing Duck


@MooingDuck은 다소 오해의 소지가 있습니다.
Euri Pinhollow

1
@ FranklinYu 이제는 해당 의견에 대한 문제를 볼 수 없기 때문에 요청한 것이 좋습니다 (의견에 답변을 제공하려는 경우 제외).
Euri Pinhollow

답변:


1572

가상 소멸자는 기본 클래스에 대한 포인터를 통해 파생 클래스의 인스턴스를 잠재적으로 삭제할 수있는 경우에 유용합니다.

class Base 
{
    // some virtual methods
};

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

여기에서 Base의 소멸자를로 선언하지 않았다는 것을 알 수 있습니다 virtual. 이제 다음 스 니펫을 살펴 보겠습니다.

Base *b = new Derived();
// use b
delete b; // Here's the problem!

자료의 소멸자가 아니므로 virtual하고 bA는 Base*A와 가리키는 Derived객체 delete b정의되지 않은 동작 :

[In delete b]에서 삭제할 객체의 정적 유형이 동적 유형과 다른 경우 정적 유형은 삭제할 객체의 동적 유형의 기본 클래스 여야하며 정적 유형에는 가상 소멸자 또는 동작이 정의되지 않았습니다 .

대부분의 구현에서 소멸자에 대한 호출은 가상이 아닌 코드와 같이 해결됩니다. 즉, 기본 클래스의 소멸자는 호출되지만 파생 클래스 중 하나는 호출되지 않으므로 리소스가 누출됩니다.

요약하면 항상 기본 클래스의 소멸자를 만듭니다. virtual 는 다형성으로 조작 될 때 해야합니다.

기본 클래스 포인터를 통한 인스턴스 삭제를 방지하려면 기본 클래스 소멸자를 보호 및 비 가상화로 만들 수 있습니다. 그렇게하면 컴파일러가 호출 할 수 없습니다.delete 에서 기본 클래스 포인터 .

Herb Sutter의이 기사에서 가상 및 가상 기본 클래스 소멸자에 대해 자세히 알아볼 수 있습니다 .


174
이것은 내가 전에 만든 공장을 사용하여 왜 누수가 발생했는지 설명합니다. 이제 모든 것이 이해됩니다. 감사합니다
Lodle

8
글쎄, 이것은 데이터 멤버가 없기 때문에 나쁜 예입니다. 만약에 BaseDerived있는 모든 자동 스토리지 변수를? 즉, 소멸자에서 실행할 "특별한"또는 추가 사용자 정의 코드가 없습니다. 그러면 소멸자를 작성하지 않아도 될까요? 아니면 파생 클래스에 여전히 메모리 누수가 있습니까?
bobobobo


28
Herb Sutter의 기사에서 : "가이드 라인 # 4 : 기본 클래스 소멸자는 공개 및 가상이거나 보호 및 비 가상적이어야합니다."
순대

3
또한 기사에서- '가상 소멸자없이 다형성으로 삭제하면, "정의되지 않은 행동"이라는 무서운 유령을 소환합니다. lol
Bondolin

219

가상 생성자는 불가능하지만 가상 소멸자는 가능합니다. 실험 해 보자 .......

#include <iostream>

using namespace std;

class Base
{
public:
    Base(){
        cout << "Base Constructor Called\n";
    }
    ~Base(){
        cout << "Base Destructor called\n";
    }
};

class Derived1: public Base
{
public:
    Derived1(){
        cout << "Derived constructor called\n";
    }
    ~Derived1(){
        cout << "Derived destructor called\n";
    }
};

int main()
{
    Base *b = new Derived1();
    delete b;
}

위의 코드는 다음을 출력합니다.

Base Constructor Called
Derived constructor called
Base Destructor called

파생 객체의 생성은 생성 규칙을 따르지만 "b"포인터 (기본 포인터)를 삭제하면 기본 소멸자 만 호출됩니다. 그러나 이것은 일어나서는 안됩니다. 적절한 작업을 수행하려면 기본 소멸자를 가상으로 만들어야합니다. 이제 다음에서 어떤 일이 발생하는지 봅시다.

#include <iostream>

using namespace std;

class Base
{ 
public:
    Base(){
        cout << "Base Constructor Called\n";
    }
    virtual ~Base(){
        cout << "Base Destructor called\n";
    }
};

class Derived1: public Base
{
public:
    Derived1(){
        cout << "Derived constructor called\n";
    }
    ~Derived1(){
        cout << "Derived destructor called\n";
    }
};

int main()
{
    Base *b = new Derived1();
    delete b;
}

출력은 다음과 같이 변경되었습니다.

Base Constructor Called
Derived Constructor called
Derived destructor called
Base destructor called

따라서 기본 포인터의 파괴 (파생 객체에 할당 됨)는 파괴 규칙, 즉 먼저 파생 된 다음베이스를 따릅니다. 반면에 가상 생성자와 같은 것은 없습니다.


1
"가상 생성자는 불가능"은 가상 생성자를 직접 작성할 필요가 없음을 의미합니다. 파생 객체의 구성은 파생 체인에서 기본 구성 체인을 따라야합니다. 따라서 생성자에 대한 가상 키워드를 작성할 필요가 없습니다. 감사합니다
Tunvir Rahman Tusher

4
@Murkantilism, "가상 생성자를 수행 할 수 없습니다"는 사실입니다. 생성자를 가상으로 표시 할 수 없습니다.
cmeub

1
@cmeub, 그러나 가상 생성자에서 원하는 것을 달성하는 관용구가 있습니다.
cape1232

@TunvirRahmanTusher 왜 Base Destructor가 호출되는지 설명해 주시겠습니까?
rimalonfire 8

@rimiro 그것은 자동으로 C ++. 당신은 링크를 따라갈 수 있습니다 stackoverflow.com/questions/677620/…
Tunvir Rahman Tusher

195

다형성 기본 클래스에서 소멸자를 가상으로 선언하십시오. Scott Meyers의 Effective C ++ 항목 7입니다 . 마이어스는 클래스가있는 경우 것으로 요약에 간다 어떤 가상 함수를,이 가상 소멸자가 있어야하고, 그 클래스는 기본 클래스로 설계되지 또는 다형 적으로 사용할 수 있도록한다 설계되지 아니를 가상 소멸자를 선언합니다.


14
+ "클래스에 가상 함수가있는 경우 가상 소멸자가 있어야하며 기본 클래스로 설계되지 않았거나 다형성으로 사용하도록 설계되지 않은 클래스는 가상 소멸자를 선언해서는 안됩니다.": 이 규칙을 어기시겠습니까? 그렇지 않은 경우, 컴파일러가이 조건을 점검하고 오류가 발생하면 오류가 발생하도록하는 것이 합리적입니까?
조르지오

@Giorgio 나는 규칙에 대한 예외를 모른다. 그러나 나는 C ++ 전문가라고 평가하지 않기 때문에 이것을 별도의 질문으로 게시 할 수 있습니다. 컴파일러 경고 (또는 정적 분석 도구의 경고)가 의미가 있습니다.
Bill the Lizard

10
클래스는 특정 유형의 포인터를 통해 삭제되지 않도록 설계되었지만 여전히 가상 기능을 가지고 있습니다. 일반적인 예는 콜백 인터페이스입니다. 구독 전용이므로 콜백 인터페이스 포인터를 통해 구현을 삭제하지 않지만 가상 기능이 있습니다.
dascandy

3
@dascandy 정확히 - 그 또는 모든 많은 우리가 다형성 (polymorphic)를 사용하지만 포인터를 통해 스토리지 관리를 수행하지 않는 다른 상황 - 만 관찰 경로로 사용되는 포인터로, 예를 들어 자동 또는 정전기 기간 개체를 유지. 그러한 경우 가상 소멸자를 구현할 필요가 없습니다. 여기서 사람들을 인용하기 때문에 위에서 언급 한 Sutter를 선호합니다. "가이드 라인 # 4 : 기본 클래스 소멸자는 공개 및 가상이거나 보호되고 비 가상적이어야합니다." 후자는 실수로 기본 포인터를 통해 삭제하려고 시도하는 사람에게 방법의 오류가 표시되도록합니다.
underscore_d

1
@Giorgio 실제로 소멸자에 대한 가상 호출을 사용하고 피할 수있는 트릭이 있습니다 const Base& = make_Derived();. 이 경우 Derived가상이 아닌 경우에도 prvalue 의 소멸자 가 호출되므로 vtables / vpointer에서 발생하는 오버 헤드가 절약됩니다. 물론 범위는 매우 제한적입니다. Andrei Alexandrescu는 그의 책 Modern C ++ Design 에서 이것을 언급했다 .
vsoftco

46

또한 가상 소멸자가 없을 때 기본 클래스 포인터를 삭제하면 동작정의되지 않습니다 . 내가 최근에 배운 것 :

C ++에서 삭제 재정의는 어떻게 작동합니까?

나는 수년 동안 C ++을 사용해 왔지만 여전히 교수형에 처해있다.


나는 당신의 그 질문을 보았고 당신이 기본 소멸자를 가상으로 선언 한 것을 보았습니다. 따라서 "가상 소멸자가 없을 때 기본 클래스 포인터를 삭제하면 정의되지 않은 동작이 발생합니다"라는 질문에 대해 유효한 상태로 유지됩니까? 이 질문에서 delete를 호출하면 파생 클래스 (새로운 연산자로 생성)가 호환 가능한 버전인지 먼저 확인합니다. 그곳에서 발견되었으므로 호출되었습니다. 따라서 "소멸자가 없을 때 기본 클래스 포인터를 삭제하면 정의되지 않은 동작이 발생합니다"라고 말하는 것이 낫다고 생각하지 않습니까?
ubuntugod

그것은 거의 똑같습니다. 기본 생성자는 가상이 아닙니다.
BigSandwich

41

클래스가 다형성 일 때마다 소멸자를 가상으로 만듭니다.


13

기본 클래스에 대한 포인터를 통해 소멸자 호출

struct Base {
  virtual void f() {}
  virtual ~Base() {}
};

struct Derived : Base {
  void f() override {}
  ~Derived() override {}
};

Base* base = new Derived;
base->f(); // calls Derived::f
base->~Base(); // calls Derived::~Derived

가상 소멸자 호출은 다른 가상 함수 호출과 다르지 않습니다.

의 경우 base->f()호출이에 전달되고 재정의 함수 Derived::f()와 동일 하게 호출됩니다.base->~Base()Derived::~Derived()

소멸자가 간접적으로 호출되는 경우에도 마찬가지입니다 (예 :) delete base;. delete문 의지 호출 base->~Base()에 파견 될 것이다Derived::~Derived() .

비 가상적 소멸자가있는 추상 클래스

기본 클래스에 대한 포인터를 통해 객체를 삭제하지 않으려는 경우 가상 소멸자가 필요하지 않습니다. protected실수로 호출되지 않도록하기 만하면 됩니다.

// library.hpp

struct Base {
  virtual void f() = 0;

protected:
  ~Base() = default;
};

void CallsF(Base& base);
// CallsF is not going to own "base" (i.e. call "delete &base;").
// It will only call Base::f() so it doesn't need to access Base::~Base.

//-------------------
// application.cpp

struct Derived : Base {
  void f() override { ... }
};

int main() {
  Derived derived;
  CallsF(derived);
  // No need for virtual destructor here as well.
}

~Derived()그냥 파생 된 경우에도 모든 파생 클래스에서 명시 적으로 선언해야 ~Derived() = default합니까? 아니면 언어에 의해 암시됩니까 (생략해도 안전합니까)?
Ponkadoodle

@Wallacoloo 아니오, 필요할 때만 선언하십시오. 예를 들어 protected섹션을 넣거나을 사용하여 가상인지 확인하십시오 override.
Abyx

9

인터페이스와 인터페이스 구현에 대해 생각하고 싶습니다. C ++에서 speak 인터페이스는 순수한 가상 클래스입니다. 소멸자는 인터페이스의 일부이며 구현 될 것으로 예상됩니다. 따라서 소멸자는 순수 가상이어야합니다. 생성자는 어떻습니까? 객체는 항상 명시 적으로 인스턴스화되므로 생성자는 실제로 인터페이스의 일부가 아닙니다.


2
같은 질문에 대한 다른 관점입니다. 기본 클래스 대 파생 클래스 대신 인터페이스 측면에서 생각하면 당연한 결론입니다. 인터페이스가 가상 인터페이스보다 인터페이스의 일부라면. 그렇지 않은 경우.
Dragan Ostojic 18

2
객체 지향의 개념의 유사성을 알리는 +1 인터페이스 와 C ++ 순수 가상 클래스를 . 소멸자 와 관련하여 구현 될 것으로 예상됩니다 . 종종 불필요합니다. 클래스가 동적으로 할당 된 원시 메모리 (예 : 스마트 포인터를 통하지 않음), 파일 핸들 또는 데이터베이스 핸들과 같은 리소스를 관리하지 않는 한 컴파일러에서 만든 기본 소멸자를 사용하면 파생 클래스에서 문제가 없습니다. 그리고 소멸자 (또는 함수)가 virtual기본 클래스에서 virtual선언되면 선언되지 않은 경우에도 자동으로 파생 클래스에 있습니다.
DavidRR

이것은 소멸자가 반드시 인터페이스의 일부 가 아니라는 중요한 세부 사항을 놓칩니다 . 다형성 함수는 있지만 호출자가 관리하지 못하거나 삭제할 수없는 클래스를 쉽게 프로그래밍 할 수 있습니다. 그런 다음 가상 소멸자는 목적이 없습니다. 물론, 이것을 보장하기 위해 비가 상-아마도 기본-소멸자는 비공개이어야합니다. 내가 추측해야한다면, 그러한 클래스가 프로젝트에 내부적으로 더 자주 사용된다고 말하지만,이 모든 것이 예제 / 뉘앙스와 관련성이 적지는 않습니다.
underscore_d

8

기본 클래스 포인터를 통해 객체를 삭제하는 동안 다른 소멸자가 올바른 순서를 따라야 할 경우 소멸자에 대한 가상 키워드가 필요합니다. 예를 들면 다음과 같습니다.

Base *myObj = new Derived();
// Some code which is using myObj object
myObj->fun();
//Now delete the object
delete myObj ; 

기본 클래스 소멸자가 가상이면 객체는 순서대로 소멸됩니다 (먼저 파생 된 객체 다음 base). 기본 클래스 소멸자가 가상이 아닌 경우 기본 클래스 오브젝트 만 삭제됩니다 (포인터가 기본 클래스 "Base * myObj"이므로). 따라서 파생 객체에 대한 메모리 누수가 발생합니다.


7

간단하게 말하면 가상 소멸자는 파생 클래스 객체를 가리키는 기본 클래스 포인터를 삭제할 때 적절한 순서로 리소스를 파괴하는 것입니다.

 #include<iostream>
 using namespace std;
 class B{
    public:
       B(){
          cout<<"B()\n";
       }
       virtual ~B(){ 
          cout<<"~B()\n";
       }
 };
 class D: public B{
    public:
       D(){
          cout<<"D()\n";
       }
       ~D(){
          cout<<"~D()\n";
       }
 };
 int main(){
    B *b = new D();
    delete b;
    return 0;
 }

OUTPUT:
B()
D()
~D()
~B()

==============
If you don't give ~B()  as virtual. then output would be 
B()
D()
~B()
where destruction of ~D() is not done which leads to leak


기본 가상 소멸자가없고 delete기본 포인터를 호출 하면 정의되지 않은 동작이 발생합니다.
제임스 애드키 슨

@JamesAdkison 왜 정의되지 않은 동작으로 이어 집니까?
rimalonfire

@rimiro 그것은 표준이 말하는 것 입니다. 사본이 없지만 링크를 사용하면 표준 내 위치를 참조하는 주석으로 이동합니다.
제임스 Adkison

@rimiro "따라서, 삭제가 기본 클래스 인터페이스를 통해 다형성으로 수행 될 수 있다면, 사실상 행동해야하고 가상이어야합니다. 실제로 언어는 그것을 요구합니다-가상 소멸자없이 다형성을 삭제하면, 당신은 두려운 스펙터를 소환합니다 "정의되지 않은 행동", 내가 개인적으로 조명이 어두운 골목에서 만나지 않는 유령은 대단히 감사합니다. " ( gotw.ca/publications/mill18.htm)- 허브 셔터
제임스 애드키 슨

4

가상 기본 클래스 소멸자는 "모범 사례"입니다. 항상 메모리 누수를 방지하기 위해 반드시 사용해야합니다. 그것들을 사용하면 클래스의 상속 체인에있는 모든 소멸자가 (적절한 순서로) 호출되고 있는지 확인할 수 있습니다. 가상 소멸자를 사용하여 기본 클래스에서 상속하면 상속 클래스의 소멸자가 자동으로 가상으로 만들어집니다 (따라서 상속 클래스 소멸자 선언에서 '가상'을 다시 입력 할 필요가 없습니다).


4

shared_ptr(unique_ptr이 아닌 shared_ptr 만 사용 ) 기본 클래스 소멸자를 가상으로 가질 필요는 없습니다.

#include <iostream>
#include <memory>

using namespace std;

class Base
{
public:
    Base(){
        cout << "Base Constructor Called\n";
    }
    ~Base(){ // not virtual
        cout << "Base Destructor called\n";
    }
};

class Derived: public Base
{
public:
    Derived(){
        cout << "Derived constructor called\n";
    }
    ~Derived(){
        cout << "Derived destructor called\n";
    }
};

int main()
{
    shared_ptr<Base> b(new Derived());
}

산출:

Base Constructor Called
Derived constructor called
Derived destructor called
Base Destructor called

이것이 가능하지만, 나는 이것을 사용하지 않는 것이 좋습니다. 가상 소멸자의 오버 헤드는 아주 작기 때문에 경험이 부족한 프로그래머가이 사실을 모르는 경우가 있습니다. 그 작은 virtual키워드는 많은 고통에서 당신을 구할 수 있습니다.
Michal Štein

3

가상 소멸자 또는 가상 소멸자를 사용하는 방법

클래스 소멸자는 클래스 앞에 할당 된 메모리를 재 할당하기 위해 ~로 시작하는 클래스와 동일한 이름을 가진 함수입니다. 가상 소멸자가 필요한 이유

일부 가상 기능이있는 다음 샘플을 참조하십시오.

이 샘플은 또한 글자를 상한 또는 하한으로 변환하는 방법을 알려줍니다

#include "stdafx.h"
#include<iostream>
using namespace std;
// program to convert the lower to upper orlower
class convertch
{
public:
  //void convertch(){};
  virtual char* convertChar() = 0;
  ~convertch(){};
};

class MakeLower :public convertch
{
public:
  MakeLower(char *passLetter)
  {
    tolower = true;
    Letter = new char[30];
    strcpy(Letter, passLetter);
  }

  virtual ~MakeLower()
  {
    cout<< "called ~MakeLower()"<<"\n";
    delete[] Letter;
  }

  char* convertChar()
  {
    size_t len = strlen(Letter);
    for(int i= 0;i<len;i++)
      Letter[i] = Letter[i] + 32;
    return Letter;
  }

private:
  char *Letter;
  bool tolower;
};

class MakeUpper : public convertch
{
public:
  MakeUpper(char *passLetter)
  {
    Letter = new char[30];
    toupper = true;
    strcpy(Letter, passLetter);
  }

  char* convertChar()
  {   
    size_t len = strlen(Letter);
    for(int i= 0;i<len;i++)
      Letter[i] = Letter[i] - 32;
    return Letter;
  }

  virtual ~MakeUpper()
  {
    cout<< "called ~MakeUpper()"<<"\n";
    delete Letter;
  }

private:
  char *Letter;
  bool toupper;
};


int _tmain(int argc, _TCHAR* argv[])
{
  convertch *makeupper = new MakeUpper("hai"); 
  cout<< "Eneterd : hai = " <<makeupper->convertChar()<<" ";     
  delete makeupper;
  convertch *makelower = new MakeLower("HAI");;
  cout<<"Eneterd : HAI = " <<makelower->convertChar()<<" "; 
  delete makelower;
  return 0;
}

위 샘플에서 MakeUpper 및 MakeLower 클래스의 소멸자가 호출되지 않았 음을 알 수 있습니다.

가상 소멸자가있는 다음 샘플을 참조하십시오.

#include "stdafx.h"
#include<iostream>

using namespace std;
// program to convert the lower to upper orlower
class convertch
{
public:
//void convertch(){};
virtual char* convertChar() = 0;
virtual ~convertch(){}; // defined the virtual destructor

};
class MakeLower :public convertch
{
public:
MakeLower(char *passLetter)
{
tolower = true;
Letter = new char[30];
strcpy(Letter, passLetter);
}
virtual ~MakeLower()
{
cout<< "called ~MakeLower()"<<"\n";
      delete[] Letter;
}
char* convertChar()
{
size_t len = strlen(Letter);
for(int i= 0;i<len;i++)
{
Letter[i] = Letter[i] + 32;

}

return Letter;
}

private:
char *Letter;
bool tolower;

};
class MakeUpper : public convertch
{
public:
MakeUpper(char *passLetter)
{
Letter = new char[30];
toupper = true;
strcpy(Letter, passLetter);
}
char* convertChar()
{

size_t len = strlen(Letter);
for(int i= 0;i<len;i++)
{
Letter[i] = Letter[i] - 32;
}
return Letter;
}
virtual ~MakeUpper()
{
      cout<< "called ~MakeUpper()"<<"\n";
delete Letter;
}
private:
char *Letter;
bool toupper;
};


int _tmain(int argc, _TCHAR* argv[])
{

convertch *makeupper = new MakeUpper("hai");

cout<< "Eneterd : hai = " <<makeupper->convertChar()<<" \n";

delete makeupper;
convertch *makelower = new MakeLower("HAI");;
cout<<"Eneterd : HAI = " <<makelower->convertChar()<<"\n ";


delete makelower;
return 0;
}

가상 소멸자는 클래스에서 가장 파생 된 런타임 소멸자를 명시 적으로 호출하여 적절한 방식으로 오브젝트를 지울 수 있습니다.

또는 링크를 방문하십시오

https://web.archive.org/web/20130822173509/http://www.programminggallery.com/article_details.php?article_id=138


2

기본 클래스에서 파생 클래스 소멸자를 호출해야 할 때. 기본 클래스에서 가상 기본 클래스 소멸자를 선언해야합니다.


2

이 질문의 핵심은 소멸자가 아닌 가상 방법과 다형성에 관한 것이라고 생각합니다. 보다 명확한 예는 다음과 같습니다.

class A
{
public:
    A() {}
    virtual void foo()
    {
        cout << "This is A." << endl;
    }
};

class B : public A
{
public:
    B() {}
    void foo()
    {
        cout << "This is B." << endl;
    }
};

int main(int argc, char* argv[])
{
    A *a = new B();
    a->foo();
    if(a != NULL)
    delete a;
    return 0;
}

인쇄합니다 :

This is B.

virtual그것이 없으면 인쇄됩니다 :

This is A.

이제 가상 소멸자를 언제 사용해야하는지 이해해야합니다.


아니요, 이것은 가상 함수의 기본을 완전히 다시 읽음으로써 소멸자가 언제 / 왜 하나가되어야하는지에 대한 뉘앙스를 완전히 무시합니다. ? (또한, 왜 불필요한 동적 할당이 여기에 그냥 할 B b{}; A& a{b}; a.foo();. 점검 NULL하는이어야 - nullptr- 전에 delete잘못된 indendation으로 - - 보내고 필요하지 않습니다 : delete nullptr;. 무 조작으로 정의된다 아무것도, 당신은 호출하기 전에이 문제를 확인해야하는 경우 ->foo(), 달리 new실패 하면 정의되지 않은 동작이 발생할 수 있습니다 .)
underscore_d

2
포인터 를 호출 delete하는 것이 안전합니다 NULL(즉, if (a != NULL)경비원이 필요하지 않습니다 ).
James Adkison

@SaileshD 예, 알고 있습니다. 그것이 나의 의견
James Adkison

1

가상 정의자가없는 기본 클래스 (/ struct)를 통해 삭제하거나 더 정확하게 vtable이없는 경우 "정의되지 않은"동작 또는 적어도 "충돌"정의되지 않은 동작에 대해 논의하는 것이 도움이 될 것이라고 생각했습니다. 아래 코드는 몇 가지 간단한 구조체를 나열합니다 (클래스에서도 마찬가지입니다).

#include <iostream>
using namespace std;

struct a
{
    ~a() {}

    unsigned long long i;
};

struct b : a
{
    ~b() {}

    unsigned long long j;
};

struct c : b
{
    ~c() {}

    virtual void m3() {}

    unsigned long long k;
};

struct d : c
{
    ~d() {}

    virtual void m4() {}

    unsigned long long l;
};

int main()
{
    cout << "sizeof(a): " << sizeof(a) << endl;
    cout << "sizeof(b): " << sizeof(b) << endl;
    cout << "sizeof(c): " << sizeof(c) << endl;
    cout << "sizeof(d): " << sizeof(d) << endl;

    // No issue.

    a* a1 = new a();
    cout << "a1: " << a1 << endl;
    delete a1;

    // No issue.

    b* b1 = new b();
    cout << "b1: " << b1 << endl;
    cout << "(a*) b1: " << (a*) b1 << endl;
    delete b1;

    // No issue.

    c* c1 = new c();
    cout << "c1: " << c1 << endl;
    cout << "(b*) c1: " << (b*) c1 << endl;
    cout << "(a*) c1: " << (a*) c1 << endl;
    delete c1;

    // No issue.

    d* d1 = new d();
    cout << "d1: " << d1 << endl;
    cout << "(c*) d1: " << (c*) d1 << endl;
    cout << "(b*) d1: " << (b*) d1 << endl;
    cout << "(a*) d1: " << (a*) d1 << endl;
    delete d1;

    // Doesn't crash, but may not produce the results you want.

    c1 = (c*) new d();
    delete c1;

    // Crashes due to passing an invalid address to the method which
    // frees the memory.

    d1 = new d();
    b1 = (b*) d1;
    cout << "d1: " << d1 << endl;
    cout << "b1: " << b1 << endl;
    delete b1;  

/*

    // This is similar to what's happening above in the "crash" case.

    char* buf = new char[32];
    cout << "buf: " << (void*) buf << endl;
    buf += 8;
    cout << "buf after adding 8: " << (void*) buf << endl;
    delete buf;
*/
}

가상 소멸자가 필요한지 여부는 제안하지 않지만 일반적으로 가상 소멸자를 사용하는 것이 좋습니다. 기본 클래스 (/ struct)에 vtable이없고 파생 클래스 (/ struct)가 있고 기본 클래스 (/ struct)를 통해 객체를 삭제하면 충돌이 발생할 수있는 이유를 지적하고 있습니다. 바늘. 이 경우 힙의 사용 가능 루틴으로 전달한 주소가 유효하지 않으므로 충돌의 원인이됩니다.

위의 코드를 실행하면 문제가 발생했을 때 명확하게 표시됩니다. 기본 클래스 (/ struct)의 this 포인터가 파생 클래스 (/ struct)의 this 포인터와 다르면이 문제가 발생합니다. 위의 샘플에서 구조체 a와 b에는 vtable이 없습니다. 구조체 c와 d에는 vtable이 있습니다. 따라서 ac 또는 d 객체 인스턴스에 대한 a 또는 b 포인터는 vtable을 설명하기 위해 고정됩니다. 이 a 또는 b 포인터를 전달하여 삭제하면 주소가 힙의 사용 가능 루틴에 유효하지 않기 때문에 충돌이 발생합니다.

기본 클래스 포인터에서 vtable이있는 파생 인스턴스를 삭제하려는 경우 기본 클래스에 vtable이 있는지 확인해야합니다. 이를 수행하는 한 가지 방법은 가상 소멸자를 추가하는 것입니다. 가상 소멸자는 어쨌든 리소스를 올바르게 정리할 수 있습니다.


0

기본 정의 virtual는 클래스의 멤버 함수가 파생 클래스에서 재정의 될 수 있는지 여부를 결정하는 것입니다.

클래스의 D-tor는 기본적으로 범위의 끝에서 호출되지만 예를 들어 힙 (동적 할당)에서 인스턴스를 정의 할 때 문제가 발생하므로 수동으로 삭제해야합니다.

명령어가 실행 되 자마자 기본 클래스 소멸자가 호출되지만 파생 된 클래스 소멸자는 호출되지 않습니다.

실제 예는 컨트롤 필드에서 이펙터, 액추에이터를 조작해야하는 경우입니다.

스코프의 끝에서 전원 요소 (액추에이터) 중 하나의 소멸자가 호출되지 않으면 치명적인 결과가 발생합니다.

#include <iostream>

class Mother{

public:

    Mother(){

          std::cout<<"Mother Ctor"<<std::endl;
    }

    virtual~Mother(){

        std::cout<<"Mother D-tor"<<std::endl;
    }


};

class Child: public Mother{

    public:

    Child(){

        std::cout<<"Child C-tor"<<std::endl;
    }

    ~Child(){

         std::cout<<"Child D-tor"<<std::endl;
    }
};

int main()
{

    Mother *c = new Child();
    delete c;

    return 0;
}

-1

다형성이든 아니든 공개적으로 상속되는 클래스에는 가상 소멸자가 있어야합니다. 다시 말해, 기본 클래스 포인터로 가리킬 수 있으면 기본 클래스에 가상 소멸자가 있어야합니다.

가상 인 경우 파생 클래스 소멸자가 호출되면 기본 클래스 생성자가 호출됩니다. 가상이 아닌 경우 기본 클래스 소멸자 만 호출됩니다.


나는 이것이 "기본 클래스 포인터에 의해 지적 될 수 있다면"필요 하고 공개적으로 삭제 될 수 있다고 말할 것이다 . 그러나 나중에 가상 dtor를 추가하는 습관을들이는 것이 아프지 않다고 생각합니다.
underscore_d
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.