Нравится? Делимся информацией!

среда, 14 ноября 2012 г.

Исследование виртуальных деструкторов

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



    Источников куча:
            Википедия  

    Проверяем:
#include <iostream>

class CBase
{
public:

   ~CBase()
   {
       std::cout << "~CBase" << std::endl;
   }
};

class CDerive: public CBase
{
   int *ptr;
public:
   CDerive()
   {
   ptr = new int;
   }

   ~CDerive()
   {
       std::cout << "~CDerive" << std::endl;
       delete ptr;
   }

};


int main()
{
   CBase *ptr;

   {
   CBase base;
   }
   std::cout << "Now CBase object must have been deleted " << std::endl;

   {
   ptr = new CDerive;

   //удаляется объект базового класса  (действует принцип раннего связывания).
   //delete вызывает деструктор класса, но какого? из-за принципа раннего связывания -
   // деструктор базового класса, т.к. они не объявлены виртуальными (Павловская, с. 205).
   //поэтому объект CDerive остается жить своей жизнью по завершении программы.
   delete ptr;
   }
   std::cout << "Now CDerive object must have been deleted " << std::endl;

   std::cin.get();
   return 0;
}
Результат:
Произошла утечка памяти, т.к. деструктор базового класса ничего не знал о созданном динамическом объекте в производном классе.

Проверяем ( ставим точки останова и просматриваем переменную ptr ):



Явная утечка памяти.
Для избегания подобных ситуаций и советуют объявлять деструктор базового класса виртуальным (хотя бы деструктор базового класса надо определить виртуальным). Этим реализуется принцип позднего связывания (который и основан на виртуальных методах), который судит не по типу, а по самому объекту, для которого вызывается метод (в нашем случае деструктор).

Проверяем:
#include <iostream>

class CBase
{
public:

   virtual ~CBase()
   {
       std::cout << "~CBase" << std::endl;
   }
};

class CDerive: public CBase
{
   int *ptr;
public:
   CDerive()
   {
   ptr = new int;
   }

   ~CDerive()
   {
       std::cout << "~CDerive" << std::endl;
       delete ptr;
   }

};

int main()
{
   CBase *ptr;

   {
   CBase base;
   }
   std::cout << "Now CBase object must have been deleted " << std::endl;

   {
   ptr = new CDerive;
   delete ptr;
   }
   std::cout << "Now CDerive object must have been deleted " << std::endl;

   std::cin.get();
   return 0;
}


Как видим происходит последовательный (в обратном порядке) вызов деструкторов при выполнении операции:
delete ptr;

Проверяем:

Подытожим. Соберем в кучу некоторые рекомендации:
1) Рекомендуется делать виртуальными деструкторы для того, чтобы гарантировать правильное освобождение памяти из-под динамического объекта, поскольку
в этом случае в любой момент времени будет выбран деструктор, соответствую­щий фактическому типу объекта (Павловская, с 207)
2) Смело делайте ваш деструктор виртуальным, коль в классе присутствует хотя бы одна виртуальная функция  ( [20.7] When should my destructor be virtual?  )
3) Практически всегда деструктор делается виртуальным. Делается это для того, чтобы корректно (без утечек памяти) уничтожались объекты не только заданного класса, а и любого производного от него. ( Википедия  )
4) Всегда делайте деструкторы базовых классов либо виртуальными и открытыми, либо невиртуальными и защищенными. (  youdevelop )


 


Комментариев нет:

Отправить комментарий