Вы не можете удалить с помощью delete[]
, если память не была инициализирована с помощью new[]
. Это одна из ситуаций (из очень, очень, очень нескольких ситуаций), когда правильно вызывать деструктор объекта:
template <class T>
Array<T>::~Array()
{
for(size_t i = 0; i < m_size; i++) {
m_data[i].~T();
}
allocator.deallocate(m_data);
}
Есть несколько других изменений, которые вы, вероятно, должны сделать:
- Объект распределителя должен быть членом объекта
Array
, а не локальным объектом в его конструкторе. Хотя большинство распределителей не имеют состояния, некоторые таковы, и необходимость создания локальной копии распределителя только внутри конструктора означает, что ваш распределитель потеряет свое состояние.
- Вы должны принять исполнительное решение относительно того, хотите ли вы уничтожать объекты в том же порядке, в каком
delete[]
уничтожает объекты. В вашей ситуации вам нужно указать это самостоятельно, изменив порядок цикла for в деструкторе.
Вот как это будет выглядеть:
template <class T>
Array<T>::~Array()
{
for(size_t i = 0; i < m_size; i++) {
m_data[m_size - i - 1].~T();
}
allocator.deallocate(m_data);
}
- Я бы также убедился, что вы избегаете использования
int
для всего, что связано с размерами этого массива. Нужно привести аргумент, чтобы отдать предпочтение int64_t
, а не size_t
(хотя это оторвется от поведения STL и является решением, которое нужно принимать осторожно), но вы определенно не хотите ограничивать себя использование 32-разрядного числа со знаком или меньше (что int
в большинстве сред).
Относительно выбора не использовать delete
:
delete
ожидает, что переданный ему указатель будет отдельным дискретно размещенным объектом. Объекты, созданные с использованием размещения - new
, не размещаются таким образом. Учтите следующее:
struct point {
int32_t x, y;
};
int main() {
char memory[1024];
size_t size = 128;
point * arr = reinterpret_cast<point*>(memory);
for(size_t i = 0; i < size; i++) {
new(memory + i) point();
}
for(size_t i = 0; i < size; i++) {
arr[i].x = 5;
arr[i].y = 10;
}
for(size_t i = 0; i < size; i++) {
//undefined behavior, we're deleting objects that weren't allocated on the heap!
delete (arr + (size - i - 1));
}
}
В этом примере вообще нет динамического распределения памяти; вся используемая память выделяется в стеке. Если бы мы использовали delete
в этой ситуации, мы бы удалили указатели на память в стеке, и может произойти любое количество (неопределенных) вещей, скорее всего, включая сбой нашей программы. Вызов деструктора позволяет объекту очистить свои составляющие объекты без необходимости (в данном случае ненужного) освобождения памяти.
for(size_t i = 0; i < size; i++) {
//Correct behavior, doesn't delete stack memory
arr[size - i - 1].~point();
}
Даже в ситуации, когда эта память была выделена в куче, мы все равно будем удалять указатели, которые не были явно выделены кодом. Это запрещено языком, и легко понять, почему: одно освобождение исходной памяти стирает весь фрагмент выделенной памяти, нет необходимости освобождать отдельные фрагменты.
struct point {
int32_t x, y;
};
int main() {
char * memory = new char[1024];
size_t size = 128;
point * arr = reinterpret_cast<point*>(memory);
for(size_t i = 0; i < size; i++) {
new(memory + i) point();
}
for(size_t i = 0; i < size; i++) {
arr[i].x = 5;
arr[i].y = 10;
}
for(size_t i = 0; i < size; i++) {
//Still UB: only the first object is a discrete allocation, the rest are part of
//that original allocation. This attempts to deallocate the same chunk of memory 128 times
//delete (arr + (size - i - 1));
//This is correct
arr[size - i - 1].~point();
}
//We do need to deallocate the original memory allocated, but we only do this once.
delete[] memory;
}