unique_ptr утечка памяти полиморфизма [дубликат]

Я реализовал его так:

  package com.stack.overflow.number.in.english;  import java.util.ResourceBundle;  public class ActualImplementation {public static ResourceBundle readPropertyFile = ResourceBundle .getBundle ("NumberEnglishRepresentation");  public static void main (String [] args) {System.out.println (ActualImplementation.main (-2));  } public static String main (Целочисленное число) {int power;  // Вычисление числа цифр Целое числоOfDigits = number & gt;  0?  (int) Math.log10 ((double) number) + 1: 1;  Строковый вывод = "";  // Если число отрицательно, преобразуйте его в положительное значение append минус до // output if (Integer.signum (number) == -1) {output = "минус";  число = число & lt;  0?  число * -1: число;  } Строка stringVal = String.valueOf (число);  if (number & lt; = 20 || number == 30 || number == 40 || number == 50 || number == 60 || number == 70 || number == 80 || number == 90 |  | number == 100 || number == 1000) output + = readPropertyFile.getString (stringVal);  else {int i;  for (i = 0; i & lt; numberOfDigits; i ++) {if (number! = 0) {numberOfDigits = number & gt;  0?  (int) Math .log10 ((double) number) + 1: 1;  power = (int) Math.pow (10, numberOfDigits - 1);  // Если число равно 10 001, то сначала напечатайте десять, а затем // оставшееся значение, если (numberOfDigits & gt; = 5 & amp; & amp; номерOfDigits% 2 == 1) {power = (int) Math.pow (10, numberOfDigits - 2  );  } if (readPropertyFile.containsKey (String.valueOf (number))) output + = readPropertyFile.getString (String .valueOf (number));  else {// Поскольку числа в единицах и десятки места считываются // иначе, если (numberOfDigits & gt; 2) {output + = readPropertyFile.getString (String .valueOf (число / мощность)) + readPropertyFile.getString (String .valueOf (  мощность));  } else {output + = readPropertyFile.getString (String .valueOf (число - число% мощности));  }} number = (int) (количество% мощности);  }}} return output;  }}  

, а файл ресурсов:

  0 = ноль 1 = один 2 = два 3 = три 4 = четыре 5 = пять 6 = шесть  7 = семь 8 = восемь 9 = девять 10 = десять 11 = одиннадцать 12 = двенадцать 13 = тринадцать 14 = четырнадцать 15 пятнадцать 16 = шестнадцать 17 = семнадцать 18 = восемнадцать 19 = девятнадцать 20 = двадцать 30 = тридцать 40 = четыредцать 50 = пятьдесят 60  = шестьдесят 70 = семьдесят 80 = восемьдесят 90 = девяносто 100 = сто 1000 = тысяча 100000 = Лах  

Этот вариант реализуется до 10 лакхов только

1213
задан j0k 1 July 2013 в 15:11
поделиться

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*, указывающим на объект Derived, delete b имеет неопределенное поведение :

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

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

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

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

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

1318
ответ дан Xhynk 16 August 2018 в 01:14
поделиться
  • 1
    Это объясняет, почему у меня были массивные утечки с использованием фабрики, которую я сделал раньше. Теперь все имеет смысл. благодаря – Lodle 20 January 2009 в 14:08
  • 2
    Будет ли это работать, если указатель был недействительным *? – Lodle 20 January 2009 в 15:31
  • 3
    Нет, это не так. Указатели Void не знают о деструкторах. – Leon Timmermans 20 January 2009 в 16:01
  • 4
    Нет, это не сработает с void *. Компилятор ничего не знает о том, что указывает точка void *. Все, что он знает, это то, что это место памяти. Вам нужно указать указатель на тип, чтобы сообщить компилятору, что там. – Rob K 20 January 2009 в 16:02
  • 5
    Из статьи Херба Саттера: «Руководство № 4: деструктор базового класса должен быть открытым или виртуальным, или защищенным и не виртуальным». – Sundae 9 February 2016 в 09:22

Вызов деструктора с помощью указателя на базовый класс

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.
}
10
ответ дан Abyx 16 August 2018 в 01:14
поделиться
  • 1
    Нужно ли явно объявлять ~Derived() во всех производных классах, даже если это просто ~Derived() = default? Или это подразумевается языком (что делает его безопасным опустить)? – Ponkadoodle 18 August 2016 в 15:20
  • 2
    @Wallacoloo нет, только объявляйте его, когда это необходимо. Например. поставить protected раздел или убедиться, что он виртуальный, используя override. – Abyx 18 August 2016 в 17:51
  • 1
    + «Если класс имеет любую виртуальную функцию, он должен иметь виртуальный деструктор, а классы, не предназначенные для базовых классов или не предназначенные для использования полиморфно, не должны объявлять виртуальных деструкторов». Существуют ли случаи, когда он делает смысл нарушить это правило? Если нет, было бы ли смысл, если бы компилятор проверял это условие и выдавал ошибку, не удовлетворен ли он? – Giorgio 6 May 2012 в 10:29
  • 2
    @Giorgio Я не знаю никаких исключений из правила. Но я бы не оценил себя как эксперт на C ++, поэтому вы можете опубликовать это как отдельный вопрос. Предупреждение компилятора (или предупреждение от инструмента статического анализа) имеет смысл для меня. – Bill the Lizard 6 May 2012 в 14:08
  • 3
    Классы могут быть сконструированы так, чтобы их нельзя было удалять с помощью указателя определенного типа, но все же иметь виртуальные функции - типичным примером является интерфейс обратного вызова. Один из них не удаляет его реализацию с помощью указателя интерфейса обратного вызова, поскольку он предназначен только для подписки, но имеет виртуальные функции. – dascandy 15 January 2016 в 06:05
  • 4
    @dascandy Точно - это или все many другие ситуации, когда мы используем полиморфное поведение, но не выполняем управление хранилищем с помощью указателей. поддерживая объекты автоматической или статической продолжительности, причем указатели используются только как маршруты наблюдения. Нет необходимости / цели в реализации виртуального деструктора в любых подобных случаях. Поскольку мы просто цитируем людей здесь, я предпочитаю Саттера сверху: «Руководство № 4: деструктор базового класса должен быть либо открытым, либо виртуальным, либо защищенным и не виртуальным». Последнее гарантирует, что кто-либо случайно пытается удалить с помощью базового указателя, отображается ошибка их путей – underscore_d 23 April 2016 в 15:58
  • 5
    @Giorgio Существует фактически трюк, который можно использовать и избегать виртуального вызова деструктора: свяжите с помощью ссылки const производный объект на базу, например const Base& = make_Derived();. В этом случае деструктор Derived prvalue будет вызываться, даже если он не является виртуальным, поэтому сохраняется экономия, введенная vtables / vpointers. Разумеется, масштабы весьма ограничены. Андрей Александреску упомянул об этом в своей книге Modern C ++ Design . – vsoftco 2 November 2016 в 21:06

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

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

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

37
ответ дан Community 16 August 2018 в 01:14
поделиться
  • 1
    Я посмотрел на этот ваш вопрос и увидел, что вы объявили базового деструктора виртуальным. Таким образом, «удаление указателя базового класса, когда нет виртуального деструктора, приведет к неопределенному поведению». оставайтесь в силе в отношении этого вашего вопроса? Поскольку в этом вопросе, когда вы вызывали delete, производный класс (созданный его новым оператором) сначала проверяется на совместимую версию. Так как он нашел там, он был вызван. Итак, вы не думаете, что было бы лучше сказать, что «удаление указателя базового класса, когда нет деструктора, приведет к неопределенному поведению»? – ubuntugod 23 February 2016 в 08:09
  • 2
    Это почти то же самое. Конструктор по умолчанию не является виртуальным. – BigSandwich 27 February 2016 в 00:30

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

30
ответ дан Dana 16 August 2018 в 01:14
поделиться

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

#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

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

156
ответ дан Daniel Carvalho 16 August 2018 в 01:14
поделиться
  • 1
    & Quot; виртуальный конструктор невозможно " означает, что вам не нужно писать виртуальный конструктор самостоятельно. Построение производного объекта должно следовать цепочке строительства от производной до базы. Поэтому вам не нужно писать ключевое слово virtual для своего конструктора. благодаря – Tunvir Rahman Tusher 19 April 2013 в 07:50
  • 2
    @Murkantilism, "виртуальные конструкторы не могут быть выполнены" действительно. Конструктор не может быть помечен как виртуальный. – cmeub 21 April 2013 в 21:09
  • 3
    @cmeub, но есть идиома для достижения того, что вы хотите от виртуального конструктора. См. parashift.com/c ++ - faq-lite / virtual-ctors.html – cape1232 3 October 2013 в 13:58
  • 4
    @TunvirRahmanTusher не могли бы вы объяснить, почему называется Base Destructor ?? – rimiro 11 November 2017 в 09:49
  • 5
    @rimiro Автоматически с помощью c ++. Вы можете перейти по ссылке stackoverflow.com/questions/677620/… – Tunvir Rahman Tusher 11 November 2017 в 13:01

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

7
ответ дан Dragan Ostojic 16 August 2018 в 01:14
поделиться
  • 1
    Как это улучшается по уже принятому ответу? – cale_b 8 November 2012 в 18:50
  • 2
    Это другой взгляд на тот же вопрос. Если мы думаем в терминах интерфейсов вместо базового класса vs производного класса, то это естественный вывод: если он является частью интерфейса, чем делает его виртуальным. Если это не так. – Dragan Ostojic 9 November 2012 в 20:58
  • 3
    +1 для определения сходства концепции OO интерфейса и чистого виртуального класса C ++ . Что касается деструктора, ожидается, что он будет реализован : это часто не нужно. Если класс не управляет таким ресурсом, как необработанная динамически распределенная память (например, не с помощью интеллектуального указателя), дескриптор файла или дескриптор базы данных, использующий деструктор по умолчанию, созданный компилятором, хорош в производных классах. Обратите внимание, что если деструктор (или любая функция) объявлен virtual в базовом классе, он автоматически virtual в производном классе, даже если он не объявлен. – DavidRR 11 July 2013 в 14:22
  • 4
    Это пропустит критическую деталь, что деструктор - это не обязательно часть интерфейса. Можно легко программировать классы, которые имеют полиморфные функции, но которые вызывающий не удаляет / не может удалять. Тогда виртуальный деструктор не имеет никакой цели. Конечно, для обеспечения этого, не виртуальный - возможно, дефолт - деструктор должен быть непубличным. Если бы я должен был догадаться, я бы сказал, что такие классы чаще всего используются внутри проектов, но это не делает их менее актуальными в качестве примера / нюанса во всем этом. – underscore_d 23 April 2016 в 16:12

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

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

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

5
ответ дан Mukul Kashmira 16 August 2018 в 01:14
поделиться

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

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

1
ответ дан nickdu 16 August 2018 в 01:14
поделиться

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

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

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

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

#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

1
ответ дан peak 16 August 2018 в 01:14
поделиться

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

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.

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

0
ответ дан Peter Hall 16 August 2018 в 01:14
поделиться
  • 1
    Нет, это только восстанавливает основные основы виртуальных функций, полностью игнорируя нюансы того, когда / почему деструктор должен быть одним - что не так интуитивно, поэтому почему ОП задал вопрос. (Кроме того, почему ненужное динамическое распределение здесь? Просто сделайте B b{}; A& a{b}; a.foo();. Проверка на NULL - которая должна быть nullptr - до delete ing - с неправильным индексом - не требуется: delete nullptr; определяется как no-op. Во всяком случае, вы должны были проверить это перед вызовом ->foo(), так как иначе неопределенное поведение может произойти, если new как-то не удалось.) – underscore_d 23 April 2016 в 16:15
  • 2
    Безопасно называть delete указателем NULL (т. Е. Вам не нужен предохранитель if (a != NULL)). – James Adkison 13 October 2016 в 19:57
  • 3
    @SaileshD Да, я знаю. Вот что я сказал в моем комментарии – James Adkison 18 April 2018 в 16:02

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

 #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

6
ответ дан Prakash GiBBs 16 August 2018 в 01:14
поделиться
  • 1
    Не иметь базового виртуального деструктора и вызывать delete на базовом указателе приводит к неопределенному поведению. – James Adkison 13 October 2016 в 20:00
  • 2
    @JamesAdkison почему это приводит к неопределенному поведению? – rimiro 11 November 2017 в 12:06
  • 3
    @rimiro Это то, что говорит стандарт . У меня нет копии, но ссылка приводит вас к комментарию, где кто-то ссылается на место в стандарте. – James Adkison 11 November 2017 в 14:52
  • 4
    @rimiro "Если удаление, следовательно, может быть выполнено полиморфно через интерфейс базового класса, то оно должно вести себя практически и должно быть виртуальным. Действительно, язык требует этого - если вы удаляете полиморфно без виртуального деструктора, вы вызываете страшный призрак «неопределенного поведения», призрак, которого я лично предпочел бы встретить даже в умеренно хорошо освещенной аллее, большое спасибо. & quot; ( gotw.ca/publications/mill18.htm ) - Herb Sutter – James Adkison 11 November 2017 в 14:54

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

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

-1
ответ дан Syed H 16 August 2018 в 01:14
поделиться
  • 1
    Я бы сказал, что это необходимо только ", если на него можно указать указатель базового класса" и могут быть публично удалены. Но я думаю, что не повредить привычку добавлять виртуальные dtors, если они могут понадобиться позже. – underscore_d 23 April 2016 в 16:21

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

2
ответ дан Trantor 16 August 2018 в 01:14
поделиться

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

0
ответ дан user2641018 16 August 2018 в 01:14
поделиться
Другие вопросы по тегам:

Похожие вопросы: