Считается ли эта ситуация к утечке памяти?

Рейтинг: 0Ответов: 3Опубликовано: 16.05.2023

учусь на курсах по С++ и у меня возник вопрос про наследование - если мы приведём объект дочернего класса, который имеет свои поля(сейчас речь именно про простые переменные int,double e.t.c, c динамической памятью утечка будет, ежу понятно), к типу базового, а после удалим, вызвав тем самым деструктор базового класса, будет ли это считаться утечкой памяти, ведь память, выделенная на поля дочернего вроде как не будет освобождена

Ответы

▲ 4

Стандарт прямо говорит, что при отсутствии виртуального деструктора поведение не будет чётко определено.

7.6.2.9 Delete [expr.delete]
In a single-object delete expression, if the static type of the object to be deleted is different from its dynamic type and the selected deallocation function (see below) is not a destroying operator delete, the static type shall be a base class of the dynamic type of the object to be deleted and the static type shall have a virtual destructor or the behavior is undefined.

class base { };
class derived: public base { }; 

int main() {
    base* p = new derived();
    delete p; // The is undefined behavior!
}
▲ 2

Формально это неопределенное поведение всегда, независимо от типов полей.

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

struct A {int a;};
struct B {int b;};
struct C : A, B {};

int main()
{
    B *b = new C;
    delete b; // Краш!
}

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

В остальных случаях на практике это скорее всего будет работать. Но лучше все-таки избегать UB.

▲ 1

Всё зависит от того, что по вашему означает "приведём объект дочернего класса ... к типу базового, а после удалим". Если речь идёт о полиморфном наследовании, которое включает в себя наличие таблицы виртуальных методов (vtable), то оно реализуется через привденение к указателю или ссылке на базовый класс (upcasting). Если же вы попытаетесь скастить не к указателю и не к ссылочному типу, то вы получите UB (undefined behavior), т.е. всё что угодно.

#include <iostream>

struct Base {
    ~Base() { std::clog << "Base" << std::endl; }
};

struct Derived : Base {
    ~Derived() { std::clog << "Derived" << std::endl; }
}; 

struct VrtBase {
    virtual ~VrtBase() { std::clog << "VrtBase" << std::endl; }
};

struct VrtDerived : VrtBase {
    virtual ~VrtDerived() { std::clog << "VrtDerived" << std::endl; }
}; 

int main() {
    VrtBase* vbase_ptr = new VrtDerived;
    delete vbase_ptr; // OK

    Base* base_ptr = new Derived;
    delete base_ptr; // UB

    VrtBase base1{VrtDerived{}}; // UB

    VrtBase base2;
    base2 = static_cast<VrtBase>(VrtDerived{}); // UB

    return 0;
}

Что касается данных-членов (не примитивных), то их деструктор будет вызван сразу после деструктора владеющего класса, перед деструктором базового.