2012-06-15 3 views
2

я делаю небольшой эксперимент с виртуальными деструкторов для обзора - интересно, если кто имеет простое объяснение следующее (используя против 2010):Виртуальные деструкторы C++ в цепочке наследования уровня 4.

Я определяю иерархии классов ABCD, D наследует C, C наследует B, B наследует A, A - база;

провел 2 эксперимента:

Первый эксперимент -

А имеет виртуальный деструктор.

В имеет не-виртуальный деструктор

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

D имеет не виртуальный деструктор

// -------------- --------------

Отметьте 4 объекта на куче типа D - Наведите указатель на A *, B * и C * на первые 3 - Оставьте четвертый как D * для полноты. Удалить все 4 указателя.

Как и ожидалось, во всех четырех случаях целая цепь деструктора выполняется в обратном порядке от D до A, освобождая всю память.

Второй эксперимент -

А имеет не-виртуальный деструктор ** изменился к не виртуальной

B имеет не виртуальный деструктор

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

D имеет не виртуальный Distructor

Отметьте 4 объекта на куче типа D - Наведите указатель на A *, B * и C * на первые 3 - Оставьте четвертый как D * для полноты.

Удаление указателей C * и D *: целая цепь деструктора выполняется в обратном порядке от D до A, освобождая всю память.

Удаление B *: B, а затем деструктор запускается (утечка)

Удаление *: Только деструктор запускается (течи)

Может кто-нибудь объяснить, почему это?

Когда в эксперименте 2 выделены D-типы, его непосредственный базовый класс (C) имеет виртуальный деструктор - не сообщает ли компилятор его отслеживание с помощью Vptr и знает тип памяти? БЕСПЛАТНАЯ ссылка?

Благодаря Майк

+1

Было бы здорово, если бы вы предоставили код примера. – Rook

+0

@Rook Я опубликовал фрагмент кода, который иллюстрирует этот случай (почти идентично) - дайте мне знать, если есть какие-либо комментарии. –

ответ

6

Когда opjects типа D выделяются в эксперименте 2, его непосредственный базовый класс (C) имеет виртуальный деструктор - Doesnt что сказать компилятору, чтобы отслеживать его с Vptr и знать, память тип? БЕСПЛАТНАЯ ссылка?

No.

В вашем втором тесте, A и B не имеют vptrs/виртуальные таблицы. (И даже если бы они это сделали, не виртуальная функция-член все равно была бы решена статически, а не динамически.)

Другими словами, базовый класс не «наследует» информацию (например, являются ли функции виртуальными) из производных классы.

+0

Если базовый класс не наследует информацию от производных классов, почему в эксперименте 1 указывает на ссылку B на объект D (B имеет не виртуальный деструктор), все еще «знает» его распределение объектов D и запускает всю последовательность деструктора для объекта типа D? – MikeyG

+2

Потому что в первом тестовом случае деструктор 'B' ** является ** виртуальным; если функция-член является виртуальной в базовом классе, то она является виртуальной в производном классе, даже если вы не объявляете ее явно. –

+0

ОК, это имеет смысл - вот где мое недоразумение было - – MikeyG

1

Является ли ваш реальный вопрос о том, почему нужно использовать виртуальные виртуальные деструкторы? Cos, имеющий базовый класс с не виртуальным деструктором, плох. См. the faq

+0

Нет, это не вопрос. –

+0

Он читает много, как демонстрация проблем, которые приносят не виртуальные деструкторы. Все поведение, которое он описывает, кажется вполне ожидаемым. – Rook

+0

Нет - но мне не хватало заметки в faq - – MikeyG

2

Когда вы удаляете A * без виртуального деструктора, во время компиляции компилятор не знает, что он укажет на время выполнения на объект с виртуальным деструктором. Удаление может быть объекта с виртуальным деструктором - или нет. Динамическое связывание не происходит.

+1

OK - Так вы говорите, ссылка (потому что это указатель типа A) не знает, что во время компиляции она укажет на объект с виртуальным деструктором? – MikeyG

+0

(Неполный комментарий выше извините) - но в эксперименте один, при компиляции, объект B имеет виртуальный деструктор в своем базовом классе - поэтому, когда B указывает на D, произойдет динамическое связывание? Из этого можно сделать следующее: 1 - это ссылка (тип указателя), которая будет определять динамическую привязку, а не фактическую выделенную память, а 2 - до тех пор, пока где-то в цепочке наследования есть виртуальный деструктор во время компиляции, произойдет динамическое связывание? – MikeyG

+0

Беспокойство - я получил его сейчас спасибо – MikeyG

1

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

Обратите внимание, что я также добавил некоторое использование виртуальной функции в разных Ctor, чтобы проиллюстрировать, как она работает (вкратце, в каждом Ctor, V-таблица обновляется только «до нее», что означает виртуальную функцию реализация, которая будет вызываться, является самой производной до «этой точки» цепочки наследования).

Моя заметка: в примере кода, который запускает заданные классы, я также добавил создание объекта «Производный» (B и D) на STACK ->, чтобы подчеркнуть, что все соображения, касающиеся " virtual-ness "Dtor's применимы, когда мы используем указатели (любого типа) для экземпляра класса.

class A; 

void callBack(A const& a); 

class A 
{ 

    public: 
    A() { std::cout << "A Ctor " << std::endl; f1(); callBack(*this); /* f3();*/ } 
    ~A() { std::cout << "A Dtor " << std::endl; } 

    void f1() { std::cout << "A : f1 " << std::endl; } 
    virtual void f2() const { std::cout << "A : f2 " << std::endl; } 
    virtual void f3() = 0; 
}; 


class B : public A 
{ 
    public: 
    B() { std::cout << "B Ctor " << std::endl; f1(); callBack(*this); f3(); } 
    ~B() { std::cout << "B Dtor " << std::endl; } 
    void f1() { std::cout << "B : f1 " << std::endl;} 
    void f2() const { std::cout << "B : f2 " << std::endl; } 
    virtual void f3() { std::cout << "B : f3 " << std::endl; } 

}; 


class C : public A 
{ 
    public: 
    C() { std::cout << "C Ctor " << std::endl; f1(); callBack(*this); f3(); } 
    virtual ~C() { std::cout << "C Dtor " << std::endl; } 
    void f1() { std::cout << "C : f1" << std::endl;} 
    void f2() const { std::cout << "C : f2" << std::endl; } 
    virtual void f3() const { std::cout << "C : f3" << std::endl; } 

}; 

class D : public C 
{ 
    public: 
    D() { std::cout << "D Ctor " << std::endl; f1(); callBack(*this); } 
    ~D() { std::cout << "D Dtor " << std::endl; } 
    void f1() { std::cout << "D : f1" << std::endl; } 
    void f2() const { std::cout << "D : f2 " << std::endl; } 
    virtual void f3() { std::cout << "D : f3 " << std::endl; } 

}; 

void callBack(A const& a) { a.f2(); } 

// ================================================================================================================================= 

int main() 
{ 
    std::cout << "Start of main program" << std::endl; 

    std::cout << "Creating a D object on the heap" << std::endl; 
    D* pd = new D; 
    C* pc = new D; 
    A* pa = new D; 

    if (true) 
    { 
     std::cout << "Entering Dummy scope # 1 and creating B object on the stack" << std::endl; 
     B b; 
     std::cout << "Leaving Dummy scope # 1 with B object within it" << std::endl; 
    } 

    if (true) 
    { 
     std::cout << "Entering Dummy scope # 2 and creating D object on the stack" << std::endl; 
     D d; 
     std::cout << "Leaving Dummy scope # 2 with D object within it" << std::endl; 
    } 

    std::cout << "Calling delete on pd (D*) which points on a D object" << std::endl; 
    delete pd; 

    std::cout << "Calling delete on pc (C*) which points on a D object" << std::endl; 
    delete pc; 

    std::cout << "Calling delete on pa (A*) which points on a D object" << std::endl; 
    delete pa; 

    std::cout << "End of main program" << std::endl; 
    return 0; 
}