Освобождение последнего элемента динамического массива

У меня есть

int * array=new int[2];

и я хотел бы освободить память последнего элемента, таким образом уменьшив выделенную память только до 1 элемента. я пытался позвонить

delete array+1;

но выдает ошибку

*** Обнаружен glibc *** skuska: free(): недопустимый указатель: 0x000000000065a020 *

Можно ли это сделать в С++ 03 без явного перераспределения?

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

Примечание2: я пытаюсь реализовать vector::pop_back


person Slazer    schedule 04.11.2012    source источник
comment
Почему нельзя просто использовать vector::pop_back? Изобретение велосипеда принесет вам больше проблем, чем пользы.   -  person Casey    schedule 04.11.2012
comment
Я пытаюсь реализовать векторный класс самостоятельно в качестве упражнения по программированию, и теперь я реализую vector::pop_back. Я использовал int* p_array_of_values в качестве базовой структуры данных.   -  person Slazer    schedule 04.11.2012


Ответы (4)


Не используйте для этого выражение new[]. Вектор работает не так. Что вы делаете, так это выделяете кусок необработанной памяти. Для этого можно использовать malloc или использовать оператор new, который отличается от нового выражения. По сути, это то, что делает функция-член reserve() для std::vector, при условии, что вы использовали распределитель по умолчанию. Он не создает реальных объектов, как это делает выражение new[].

Когда вы хотите построить элемент, вы используете размещение new, передавая ему местоположение где-то в необработанной памяти, которую вы выделили. Когда вы хотите уничтожить элемент, вы напрямую вызываете его деструктор. Когда вы закончите, вместо использования выражения delete[] вы используете operator delete, если вы использовали operator new, или вы используете free(), если вы использовали malloc.

Вот пример создания 10 объектов и их уничтожения в обратном порядке. Я мог бы уничтожить их в любом порядке, но именно так вы бы сделали это в векторной реализации.

int main()
{
    void * storage = malloc(sizeof(MyClass) * 10);

    for (int i=0; i<10; ++i)
    {
        // this is placement new
       new ((MyClass*)storage + i) MyClass;
    }

    for (int i=9; i>=0; --i)
    {
        // calling the destructor directly
        ((MyClass*)storage + i)->~MyClass();
    }

    free(storage);
}

pop_back можно было бы реализовать, просто вызвав деструктор последнего элемента и уменьшив переменную-член size на 1. Он не должен, не должен (и не может, не создавая кучу ненужных копий) освобождать память.

person Benjamin Lindley    schedule 04.11.2012

Нет такого варианта. Единственный способ изменить размер массива - выделить новый массив размером old_size - 1, скопировать содержимое старого массива и затем удалить старый массив.

Если вам нужна свободная память для объектов, почему бы не создать массив указателей?

MyClass **arr = new MyClass*[size];
for(int i = 0; i < size; i++)
 arr[i] = new MyClass;

// ...

delete arr[size-1];
person Hauleth    schedule 04.11.2012
comment
Это вариант, но я уже использовал int* p_array_of_values в качестве базовой структуры данных. Во всяком случае, я уже нашел здесь несколько хороших решений. - person Slazer; 04.11.2012

std::vector::pop_back ничего не перераспределяет, а просто обновляет внутреннюю переменную, определяющую размер данных, уменьшая ее на единицу. Старый последний элемент все еще находится в памяти; вектор просто не позволяет вам получить к нему доступ через общедоступный API. *

Это, а также нелинейность растущего перераспределения являются причиной того, что std::vector::capacity() не эквивалентно std::vector::size().

Итак, если вы действительно пытаетесь заново изобрести std::vector по какой-либо причине, ответ на ваш вопрос о перераспределении — не надо.


* На самом деле для непримитивных типов данных все немного сложнее, так как такие элементы утрачиваются семантически, даже если их память не освобождается.

person Lightness Races in Orbit    schedule 04.11.2012

Поскольку вы используете C++03, у вас есть доступ к типу данных std::vector. Используйте это, и это один вызов:

#include <vector>
//...
std::vector<int> ary(3);
//...
ary.erase(ary.begin() + (ary.size() - 1));

or

#include <vector>
//...
std::vector<int> ary(3);
//...
ary.pop_back();

РЕДАКТИРОВАТЬ:

Почему вы пытаетесь заново изобрести велосипед? Просто используйте vector::pop_back.

В любом случае, деструктор вызывается для содержащихся типов данных, ТОЛЬКО если содержащийся тип данных НЕ ЯВЛЯЕТСЯ указателем. Если это указатель, вы должны вручную вызвать удаление для объекта, который хотите удалить, установите для него значение nullptr или NULL (поскольку попытка вызова удаления для ранее удаленного объекта плоха, вызов удаления для нулевого указателя не работает) , затем вызовите стирание.

person Casey    schedule 04.11.2012