Не удаляйте массивы через указатели на базовый класс

Никогда не удаляйте массив с помощью оператора delete[] через указатель на базовый класс. Это неопределенное поведение (Undefined Behavior). Это может приводить к утечками памяти, повреждению кучи и падению программы.

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

Неправильно:

#include <iostream>

class Base {
public:
    int x;
    virtual ~Base() { std::cout << "~Base\n"; }
};

class Derived : public Base {
public:
    int y;
    std::string s;
    ~Derived() override { std::cout << "~Derived\n"; }
};

int main() {
    Base* arr = new Derived[3];

    delete[] arr; // Проблема
}

Когда вызывается delete[], компилятор должен сделать две вещи:

  1. Вызвать деструктор для каждого элемента массива.
  2. Освободить всю выделенную память одним блоком.

Чтобы сделать первое, компилятору нужно знать точное количество элементов и их размер. Для получения количества компилятор использует мета-информацию, которая размещается прямо перед массивов. Но размер он вычислит из параметра шаблона - sizeof(Base), а не sizeof(Derived). Соответственно смещение ко второму и т.д. элементу будет вычислено неверно, а значит деструкторы будут вызваны для некорректных указателей.

В качестве решения рекомендуется использовать std::vector и умные указатели:

std::vector<std::unique_ptr<Base>> vec;

vec.push_back(std::make_unique<Derived>());
vec.push_back(std::make_unique<Derived>());

Если нужна совместимость с Си, то можно использовать массив указателей:

Base* arr[3];
for (auto& ptr: arr)
    ptr = new Derived();

// используем arr

for (auto ptr : arr)
    delete ptr;