Когда использовать виртуальные деструкторы?



У меня есть твердое понимание большинства теорий OO, но одна вещь, которая меня очень смущает, - это виртуальные деструкторы.



Я думал, что деструктор всегда вызывается, независимо от того, что и для каждого объекта в цепочке.



когда вы должны сделать их виртуальными и почему?

685   15  

15 ответов:

виртуальные деструкторы полезны, когда вы можете удалить экземпляр производного класса с помощью указателя на базовый класс:

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 это Base* указывать

объявляйте деструкторы виртуальными в полиморфном базовом классах. Это пункт 7 в Scott Meyers' Эффективный C++. Мейерс продолжает подводить итог, что если класс имеет любой виртуальная функция, она должна иметь виртуальный деструктор, и что классы, не предназначенные для базовых классов или не предназначенные для использования полиморфно должны не объявить виртуальный деструктор.

виртуальный конструктор невозможен, но виртуальный деструктор возможен. Давайте экспериментировать....

#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

Итак, уничтожение базового указателя (который принимает выделение на производный объект!) следуйте правилу уничтожения, т. е. сначала производное, а затем основание. С другой стороны для конструктора нет ничего похожего на виртуальный конструктор.

также имейте в виду, что удаление указателя базового класса при отсутствии виртуального деструктора приведет к неопределенное поведение. То, что я узнал совсем недавно:

как должно вести себя переопределение delete в 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 *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;
}

из приведенного выше примера вы можете видеть, что деструктор для класса 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

Я подумал, что было бы полезно обсудить "неопределенное" поведение или, по крайней мере, "аварийное" неопределенное поведение, которое может возникнуть при удалении через базовый класс(/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). В этом случае адрес, который вы передаете в свободную процедуру кучи, недействителен и, следовательно, является причиной сбоя.

Если вы запустите приведенный выше код, вы увидите ясно, когда возникает проблема. Когда этот указатель базового класса(/структура) отличается от этого указателя производного класса(/структура) вы столкнетесь с этой проблемой. В приведенном выше примере структуры a и b не имеют vtables. структуры c и d имеют vtables. Таким образом, указатель a или b на экземпляр объекта c или d будет исправлен для учета таблицы 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.

и теперь вы должны понять, когда использовать виртуальные деструкторы.

любой класс, который наследуется публично, полиморфный или нет, должен иметь виртуальный деструктор. Другими словами, если на него можно указать указателем базового класса, его базовый класс должен иметь виртуальный деструктор.

Если виртуальный, то вызывается деструктор производного класса, а затем конструктор базового класса. Если не виртуальный, то вызывается только деструктор базового класса.

Comments

    Ничего не найдено.