2014-06-05 2 views
1

Я думал, почему только базовый класс с виртуальным методом нуждается в виртуальном дескрипторе? взгляд на этот кусок кода (читай комментарий):Почему базовый класс без виртуальных методов нужен виртуальный деструктор?

class Base{ 
private: 
    int x; 
public: 
    Base():x(0){} 
    ~Base(){ 
     cout<<"Base dtor"<<endl; 
    } 
}; 

class Derived : public Base{ 
    int y; 
public: 
    Derived():y(0){} 
    ~Derived(){ 
     cout<<"Derived dtor"<<endl; 
    } 
}; 

int main(){ 
    Derived *pd = new Derived; 
    Base *pb = pd; 
    delete pb; // this destroys only the base part, doesn't it? 
       // so why doesnt the derived part leak? 
    return 0; 
} 

Я побежал с Valgrind и увидел, что выход был «Base dtor», и не произошло никаких утечек памяти. Итак, если был вызван только базовый класс dtor, почему не происходит утечка производного класса?

+3

Поскольку ни класс участвует в распределении ресурсов, так что нет никакой возможности утечки. (Тем не менее, то, что вы делаете, вызывает * неопределенное поведение *.) –

+2

Потому что, если у вас нет виртуальных методов, нет причин использовать такой класс. ('Base * pb = pd;') Итак, здесь, поскольку вы используете класс как полиморфный класс, вам нужен виртуальный деструктор! – Csq

+0

Попробуйте еще раз, но вместо вашего класса 'Derived', имеющего' int y', присвойте ему 'int * y' и сделайте производный конструктор присвойте' y = new int ...' и посмотрим, будет ли тогда valgrind! – druckermanly

ответ

2

Вы спросили:

почему оленьей кожу производной части утечки класса?

Когда вы звоните

delete pb; 

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

В этом случае объект, на который указывает pb, удаляется, поэтому пространство для Base::x и Derived::y удаляется.

У вас возникнет утечка памяти, если Derived::~Derived() несет ответственность за освобождение памяти.

+0

+1 это фактически правильно (если неполное обсуждение) - не уверен, почему он был занижен. –

+0

спасибо, я думаю, это действительно ответит на мой вопрос. – rooster

+0

@rooster Добро пожаловать. –

1

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

Если Derived использовано new, вы получите утечку, независимо от того, являются ли методы виртуальными или нет.

Также, как говорит Оли в комментарии, удаление только «части объекта» приводит к UB, поэтому объявление деструктора как виртуального является хорошей практикой, когда вы подозреваете, что вам может потребоваться вызвать деструктор производного объекта с помощью указателей на базовый класс.

Также вы в принципе никогда не знаете, указывает ли указатель на Base экземпляр Base или Derived, если вы не используете RTTI. Вот почему объявление деструкторов в значительной степени является «подходящим» подходом.

+1

Если производные классы не могут выделять ресурсы, определяется ли это поведение для удаления с помощью указателя базового класса, когда его деструктор не является виртуальным? – Mehrdad

+1

@Mehrdad: Стандарт просто говорит, что это всегда UB. –

+1

@OliCharlesworth: Да, это моя точка зрения, это не имеет никакого отношения к распределению ресурсов. – Mehrdad

2

Предпосылка вопроса: «только базовый класс с виртуальными методами нуждается в виртуальном деструкторе», это совершенно неправильно. Использование виртуального деструктора не имеет ничего общего с наличием (других) виртуальных методов в классе. Более правильным руководством является использование виртуального деструктора для базовых классов, который нужно будет уничтожить с помощью указателя (или ссылки) базового класса.

Как указано в других примерах, ваш пример не протекает, потому что у вас нет производного распределения, поэтому производный деструктор не является оператором. Как только вы начнете выделять в конструкторе Derived, вы получите настоящую утечку.

+0

ОК, но вопрос в том, почему сам объект Derived течет? Я имею в виду, что было выделено выделение памяти класса Derived, и только часть этого блока памяти была освобождена - часть базового класса. – rooster

+0

@rooster См. Второй абзац - пример не протекает, потому что вы не выделяете ничего в конструкторе 'Derived' и не освобождаете что-либо в своем деструкторе. Если бы у вас было освобождение, оно было бы пропущено, и пример утечки. – user4815162342

0

Есть два вопроса, под руку:

  1. Почему не производный класс в данном примере утечке памяти причины?
  2. Почему базовому классу без виртуальных методов не нужен виртуальный деструктор?

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

Для вопроса № 2 ответ заключается в том, что если ваш базовый класс не объявляет никаких виртуальных методов, то на самом деле нет смысла использовать его в качестве указателя на экземпляр производного класса для начала.


UPDATE:

Вы, кажется, непонимание термина просачивается память, которая может применяться только на динамически выделенную память:

  • переменные, которые статически (в стек или в разделе данных), автоматически освобождаются в конце выполнения своей области-объявления.

  • Переменные, динамически распределенные (в куче), должны быть освобождены «вручную», чтобы предотвратить утечку памяти.

+0

еще раз, почему сам производный объект не течет? деструктор базового класса уничтожает только базовую часть, член 'int y' объекта все еще существует – rooster

+0

@rooster: член 'int y' не динамически распределяется. Утечки памяти могут применяться только в динамически распределенной памяти! –

+0

# 2 - дискуссионный - класс не может иметь виртуальных методов, потому что его пользователи применяют его к соответствующему производному классу, как определено из другого доступного контекста. (Часто тот случай, когда классы C++ переносят объектные системы на основе C, где экземпляры несут информацию о своем типе, например gobject.) Однако может оказаться полезным удалить такие экземпляры с помощью указателя базового класса в месте, где контекст и информация о типе недоступна, например сборщик мусора. – user4815162342

0

why doesn't the derived class part leak? Поскольку в Derived конструкторе не выделяется память, используя new попытку ниже код, чтобы увидеть утечку памяти. Это модифицированный код вы дали код: резюме

#include <iostream> 
using namespace std; 
class Base{ 
private: 
    int x; 
public: 
    Base():x(0){} 
    ~Base(){ 
     cout<<"Base dtor"<<endl; 
    } 
}; 

class Derived : public Base{ 
    int y; 
    int *a; 
    public: 
    //Derived():y(0){} 
    Derived() 
    { 
     a = new int[10]; 
     y = 10; 
     cout<<"Derived ctor"; 
    } 

    ~Derived(){ 
     cout<<"Derived dtor"<<endl; 
     delete a; 
    } 
}; 


int main(){ 
    Derived *pd = new Derived; 
    Base *pb = pd; 
    delete pb; // this destroys only the base part, doesn't it? 
       // so why doesnt the derived part leak? 
    return 0; 
} 

Valgrind:

==21686== LEAK SUMMARY: 
==21686== definitely lost: 40 bytes in 1 blocks 
==21686== indirectly lost: 0 bytes in 0 blocks 
==21686==  possibly lost: 0 bytes in 0 blocks 
==21686== still reachable: 0 bytes in 0 blocks 
==21686==   suppressed: 0 bytes in 0 blocks 
Смежные вопросы