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

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

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

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

вопрос задан 20.01.2009
Lodle
11884 репутация

15 ответов


  • 1335 рейтинг

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

    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!
    

    Поскольку деструктор Base не является virtual и b является Base*, указывающим на объект Derived, delete b имеет неопределенное поведение :

    delete b], если статический тип удаляемый объект отличается от его динамического типа, статического тип должен быть базовым классом динамического типа объекта, который будет удален и статический тип должен иметь виртуальный деструктор или поведение не определено .

    В большинстве реализаций вызов деструктора будет разрешен как любой не виртуальный код, то есть будет вызываться деструктор базового класса, но не деструктор производного класса, что приводит к утечке ресурсов.

    Подводя итог, всегда делайте деструкторы базовых классов virtual, когда они предназначены для полиморфного манипулирования.

    Если вы хотите предотвратить удаление экземпляра через указатель базового класса, вы можете сделать деструктор базового класса защищенным и не виртуальным; при этом компилятор не позволит вам вызвать delete для указателя базового класса.

    Вы можете узнать больше о виртуальности и виртуальном деструкторе базового класса в этой статье от Херба Саттера .

    ответ дан Luc Touraille, с репутацией 57882, 20.01.2009
  • 168 рейтинг

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

    ответ дан Bill the Lizard, с репутацией 283013, 20.01.2009
  • 157 рейтинг

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

    #include 
    
    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 
    
    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
    

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

    ответ дан Tunvir Rahman Tusher, с репутацией 3833, 9.04.2013
  • 38 рейтинг

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

    Как должно переопределять удаление в C ++?

    Я использую C ++ в течение многих лет, и мне все еще удается повеситься.

    ответ дан BigSandwich, с репутацией 1976, 21.01.2009
  • 30 рейтинг

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

    ответ дан yesraaj, с репутацией 19948, 20.01.2009
  • 10 рейтинг

    Вызов деструктора через указатель на базовый класс

    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().

    То же самое происходит, когда деструктор вызывается косвенно, e. г. 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.
    }
    
    ответ дан Abyx, с репутацией 7834, 18.05.2015
  • 7 рейтинг

    Мне нравится думать об интерфейсах и реализациях интерфейсов. В C ++ говорят, что интерфейс является чисто виртуальным классом. Деструктор является частью интерфейса и должен быть реализован. Поэтому деструктор должен быть чисто виртуальным. Как насчет конструктора? Конструктор фактически не является частью интерфейса, потому что объект всегда создается в явном виде.

    ответ дан Dragan Ostojic, с репутацией 79, 8.11.2012
  • 6 рейтинг

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

     #include
     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
    

    ответ дан Prakash GiBBs, с репутацией 107, 26.08.2016
  • 5 рейтинг

    Виртуальное ключевое слово для деструктора необходимо, когда вы хотите, чтобы различные деструкторы следовали в правильном порядке, пока объекты удаляются через указатель базового класса. например:

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

    Если деструктор вашего производного класса является виртуальным, то объекты будут уничтожаться в порядке (сначала производный объект, затем базовый). Если деструктор вашего производного класса НЕ является виртуальным, тогда будет удален только объект базового класса (поскольку указатель имеет базовый класс "Base * myObj"). Таким образом, будет утечка памяти для производного объекта.

    ответ дан Mukul Kashmira, с репутацией 74, 29.01.2015
  • 2 рейтинг

    Виртуальные деструкторы базового класса являются «лучшей практикой» - вы всегда должны использовать их, чтобы избежать (трудно обнаружить) утечек памяти. Используя их, вы можете быть уверены, что все деструкторы в цепочке наследования ваших классов будут вызываться (в правильном порядке). Наследование от базового класса с использованием виртуального деструктора делает деструктор унаследованного класса также автоматически виртуальным (поэтому вам не нужно повторно вводить «виртуальный» в объявлении деструктора унаследованного класса).

    ответ дан Trantor, с репутацией 486, 24.01.2017
  • 1 рейтинг

    Я подумал, что было бы полезно обсудить «неопределенное» поведение или, по крайней мере, «аварийное» неопределенное поведение, которое может возникнуть при удалении через базовый класс (/ struct) без виртуального деструктора, или, точнее, без vtable. Код ниже перечисляет несколько простых структур (то же самое будет верно для классов).

    #include 
    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) указатель. В этом случае адрес, который вы передаете свободной подпрограмме кучи, является недействительным и, следовательно, причиной сбоя.

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

    Если вы планируете удалять производные экземпляры, которые имеют vtables из указателей базового класса, вы должны убедиться, что базовый класс имеет vtable. Один из способов сделать это - добавить виртуальный деструктор, который в любом случае может потребоваться для правильной очистки ресурсов.

    ответ дан nickdu, с репутацией 357, 23.03.2017
  • 1 рейтинг

    Что такое виртуальный деструктор или как использовать виртуальный деструктор

    Деструктор класса - это функция с тем же именем класса, которому предшествует ~, которая перераспределяет память, выделенную классом. Зачем нам нужен виртуальный деструктор

    Смотрите следующий пример с некоторыми виртуальными функциями

    В примере также рассказывается, как можно преобразовать букву в верхнюю или нижнюю

    #include "stdafx.h"
    #include
    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<< "called ~MakeUpper()"<<"\n";
        delete Letter;
      }
    
    private:
      char *Letter;
      bool toupper;
    };
    
    
    int _tmain(int argc, _TCHAR* argv[])
    {
      convertch *makeupper = new MakeUpper("hai"); 
      cout<< "Eneterd : hai = " <convertChar()<<" ";     
      delete makeupper;
      convertch *makelower = new MakeLower("HAI");;
      cout<<"Eneterd : HAI = " <convertChar()<<" "; 
      delete makelower;
      return 0;
    }
    

    Из приведенного выше примера вы можете видеть, что деструктор для классов MakeUpper и MakeLower не вызывается.

    Смотрите следующий пример с виртуальным деструктором

    #include "stdafx.h"
    #include
    
    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<< "called ~MakeUpper()"<<"\n";
    delete Letter;
    }
    private:
    char *Letter;
    bool toupper;
    };
    
    
    int _tmain(int argc, _TCHAR* argv[])
    {
    
    convertch *makeupper = new MakeUpper("hai");
    
    cout<< "Eneterd : hai = " <convertChar()<<" \n";
    
    delete makeupper;
    convertch *makelower = new MakeLower("HAI");;
    cout<<"Eneterd : HAI = " <convertChar()<<"\n ";
    
    
    delete makelower;
    return 0;
    }
    

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

    Или перейдите по ссылке

    https: // web. архив. орг / веб / 20130822173509 / HTTP: // WWW. programminggallery. ком / article_details. PHP? article_id = 138

    ответ дан user2578542, с репутацией 11, 17.07.2013
  • 0 рейтинг

    Я думаю, что суть этого вопроса - виртуальные методы и полиморфизм, а не деструктор. Вот более понятный пример:

    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.
    

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

    ответ дан gonjay, с репутацией 337, 18.05.2014
  • 0 рейтинг

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

    ответ дан user2641018, с репутацией 80, 9.09.2014
  • -1 рейтинг

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

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

    ответ дан Syed H, с репутацией 90, 2.01.2015