문제

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

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

언제 가상으로 만들 예정이며 그 이유는 무엇입니까?

도움이 되었습니까?

해결책

가상 파괴자는 기본 클래스에 대한 포인터를 통해 파생 클래스의 인스턴스를 삭제할 수있을 때 유용합니다.

class Base 
{
    // some virtual methods
};

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

여기서 내가 기지의 소멸자를 선언하지 않았다는 것을 알게 될 것입니다. virtual. 이제 다음 스 니펫을 살펴 보겠습니다.

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

베이스의 소멸자는 그렇지 않기 때문입니다 virtual 그리고 b a Base* a Derived 물체, delete b 가지다 정의되지 않은 행동:

안에 delete b], 삭제 될 객체의 정적 유형이 동적 유형과 다르면, 정적 유형은 삭제할 객체의 동적 유형의 기본 클래스가되어야합니다. 정적 유형은 가상 소멸자가 있거나 행동이 정의되지 않아야합니다..

대부분의 구현에서, 파괴자에 대한 호출은 비 사건 코드처럼 해결 될 것이며, 이는 기본 클래스의 소멸자가 호출되지만 파생 된 클래스 중 하나는 아니며 자원이 누출되지 않음을 의미합니다.

요약하면 항상 기본 클래스의 소멸자를 만드십시오. virtual 그들이 다형성을 조작해야 할 때.

기본 클래스 포인터를 통해 인스턴스의 삭제를 방지하려면 기본 클래스 소멸자를 보호하고 비 초가로 만들 수 있습니다. 그렇게함으로써 컴파일러는 delete 기본 클래스 포인터에서.

가상성 및 가상 기본 클래스 소멸자에 대한 자세한 내용은 Herb Sutter 의이 기사.

다른 팁

가상 생성자는 불가능하지만 가상 파괴자가 가능합니다. 실험하자 ....

#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

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

다형성 기본 클래스에서 소멸자 가상을 선언합니다. 이것은 Scott Meyers의 항목 7입니다. 효과적인 C ++. 메이어스는 수업이 어느 가상 함수, 가상 파괴자가 있어야하며, 기본 클래스로 설계되지 않았거나 다형적으로 사용되도록 설계되지 않은 클래스 ~ 아니다 가상 파괴자를 선언합니다.

또한 가상 파괴자가 없을 때 기본 클래스 포인터를 삭제하면 정의되지 않은 행동. 내가 최근에 배운 것 :

C ++의 재정의 삭제는 어떻게 행동해야합니까?

나는 몇 년 동안 C ++를 사용해 왔으며 여전히 나 자신을 매달릴 수 있습니다.

수업이 다형적 일 때마다 소멸자를 가상으로 만드십시오.

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

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.
}

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

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

 #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

기본 클래스 포인터를 통해 객체가 삭제되는 동안 다른 소멸자가 적절한 순서를 따르도록 하려면 소멸자에 대한 가상 키워드가 필요합니다.예를 들어:

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

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

가상 파괴자 란 무엇입니까?

클래스 파괴자는 ~ 이전의 클래스 이름이 같은 함수로 클래스가 할당 한 메모리를 재 할당합니다. 가상 파괴자가 필요한 이유

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

샘플은 또한 문자를 위 또는 아래로 변환 할 수있는 방법을 알려줍니다.

#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;
}

위의 샘플에서 메이크업제와 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

가상 기본 클래스 소멸자는 "모범 사례"입니다. 항상 메모리 누출을 피하기 위해 사용해야합니다. 그것들을 사용하여, 당신은 당신의 수업의 상속 체인의 모든 소멸자가 (적절한 순서로) 꿀벌을받는 것을 확인할 수 있습니다. 가상 파괴자를 사용하여 기본 클래스에서 상속하면 상속 클래스의 파괴자도 자동으로 가상으로 가상화됩니다 (따라서 상속 클래스 소멸자 선언에서 '가상'을 재현 할 필요는 없습니다).

나는 "정의되지 않은"행동, 또는 가상 소멸자가없는 기본 클래스 (/struct)를 통해 삭제할 때 발생할 수있는 "충돌"행동에 대해 논의하는 것이 도움이 될 것이라고 생각했다. 아래 코드는 몇 가지 간단한 구조를 나열합니다 (클래스에서도 마찬가지입니다).

#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) 의이 포인터가 파생 클래스 (/struct) 의이 포인터와 다를 때이 문제에 대해 실행할 것입니다. 위의 샘플에서 구조 A와 B에는 vtables가 없습니다. structs c와 d에는 vtables가 있습니다. 따라서 AC 또는 D 객체 인스턴스에 대한 A 또는 B 포인터는 VTABLE를 설명하기 위해 고정됩니다. 이 A 또는 B 포인터를 전달하면 삭제를 삭제하기 위해 주소가 유효하지 않아 충돌됩니다.

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

이 질문의 핵심은 구체적으로는 가상 방법과 다형성에 관한 것이라고 생각합니다. 다음은 명확한 예입니다.

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.

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

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

사용하는 경우 shared_ptr(고유 한 _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

공개적으로 상속 된 모든 클래스, 다형성이든 아니든 가상 파괴자가 있어야합니다. 다른 방법으로, 기본 클래스 포인터로 가리키는 경우 기본 클래스에는 가상 소멸자가 있어야합니다.

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

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top