Выделение объектов, хранящихся в векторе? - PullRequest
6 голосов
/ 28 сентября 2010

У меня есть класс, который создает вектор объектов.В деконструкторе для этого класса я пытаюсь освободить память, назначенную объектам.Я пытаюсь сделать это, просто просматривая вектор.Итак, если вектор называется map, я делаю:

Building::~Building() {
    int i;
    for (i=0; i<maps.size(); i++) {
        delete[] &maps[i];
    }
}

Когда я запускаю эту программу, программа вызывает ошибки при освобождении памяти.Я думаю, что я на самом деле удаляю массив, хранящий объекты, а не сами объекты.Это правильно?Если нет идей о том, что я делаю не так?

Ответы [ 7 ]

17 голосов
/ 28 сентября 2010

Это зависит от того, как определен вектор.

Если карты имеют значение vector<myClass*>, вы удаляете каждый элемент с чем-то похожим на:

for ( i = 0; i < maps.size(); i++)
{
    delete maps[i];
}

Если карты имеют значение vector<myClass> Iне думайте, что вам нужно удалять отдельные элементы.

13 голосов
/ 28 сентября 2010

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

Массив new и удаление массива

Что случилось с new [] и delete [], спросите вы? Эти парни используются для распределения / освобождения массивов вещей. Это могут быть POD или полноценные объекты. Для объектов они будут вызывать конструктор после выделения и деструктор при освобождении.

Давайте возьмем надуманный пример:

class MrObject
{
public:
   MrObject() : myName(new char[9]) { memcpy(myName, "MrObject", 9); }
   virtual ~MrObject() { std::cout << "Goodbye cruel world!\n"; delete [] myName; }
private:
   char* myName;
};

Теперь мы можем сделать несколько забавных вещей с MrObject.

Массивы объектов

Сначала давайте создадим красивый и простой массив:

MrObject* an_array = new MrObject[5];

Это дает нам массив из 5 MrObjects, все они хорошо инициализированы. Если мы хотим удалить этот массив, мы должны выполнить удаление массива, которое, в свою очередь, вызовет деструктор для каждого MrObject. Давайте попробуем это:

delete [] an_array;

Но что, если мы обманываем и просто делаем нормальное удаление? Что ж, сейчас самое время попробовать это для себя

delete an_array;

Вы увидите, что вызывается только первый деструктор get. Это потому, что мы не удалили весь массив, только первую запись.

Ну иногда. Это действительно не определено, что здесь происходит. Вывод состоит в том, чтобы использовать форму массива delete при использовании массива new, то же самое для простого старого new и delete.

Векторы объектов

ОК, это было весело. Но давайте теперь посмотрим на std :: vector. Вы обнаружите, что этот парень будет управлять памятью для вас, и когда он выходит из области видимости, то же самое делает и все, за что держится. Давайте возьмем его на пробную поездку:

std::vector<MrObject> a_vector(5);

Теперь у вас есть вектор с 5 инициализированными MrObjects. Давайте посмотрим, что произойдет, когда мы очистим этот присоски:

a_vector.clear();

Вы заметите, что все 5 деструкторов получили удар.

Векторы указателей на объекты

Ооооооооо вы говорите, но теперь давайте представимся. Я хочу всего хорошего в std :: vector, но также хочу управлять всей памятью сам! Ну, есть строка для этого:

std::vector<MrObject*> a_vector_of_pointers(5);
for (size_t idx = 0; idx < 5; idx++) {
   // note: it's just a regular new here, not an arra
   a_vector_of_pointers[idx] = new MrObject;
}

Видите, это было немного больше боли. Но это может быть полезно, вы можете использовать конструктор не по умолчанию при создании MrObject. Вместо этого вы можете поместить туда производные MrObjects. Ну, как вы можете видеть, что небо это предел. Но ждать! Вы создали эту память, вам лучше всего с ней справиться. Вы захотите перебрать каждую запись в векторе и очистить после себя:

for (size_t idx = 0; idx < a_vector_of_pointers.size(); idx++) {
   delete a_vector_of_pointers[idx];
}
6 голосов
/ 28 сентября 2010

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

Когда вы вставляете в вектор, вектор вызывает конструктор копирования класса, и вы фактически вставляете копию объекта. Если у вас есть функция, единственной целью которой является следующее:

void insertObj(obj & myObject)
{
  myVector.insert(myObject);
}

Затем поймите, что в этой области есть два объекта: тот, который вы передали по ссылке, и копия в векторе. Если бы вместо этого мы передавали myObject по значению, а не по ссылке, то мы могли бы сказать, что в этой области существуют две копии объекта, а одна - в вызывающей стороне. В каждом из этих 3 экземпляров они не один и тот же объект.

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

void insert()
{
  Obj myObj;
  myVector.insert(&myObj);
}

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

Суть в том, что если вы malloc'd или new'd свой объект, то вам нужно освободить или удалить его. Если вы создали его в стеке, то ничего не делайте. Вектор позаботится об этом, когда он будет уничтожен.

Для более глубокого понимания распределения на основе стека и распределения на основе кучи см. Мой ответ здесь: Как на самом деле работает автоматическое распределение памяти в C ++?

1 голос
/ 16 октября 2017

Я решил превратить свой комментарий в ответ (вместе с другими замечательными ответами здесь), так что вот так.

Я хотел бы еще раз отметить, что в данном случае речь идет о наследовании объекта.

При удалении массива производного объекта, указанного указателем Base, следующим образом:

Base* pArr = new Derived[3];

delete [] pArr;

То, что компилятор делает «под капотом», - это генерирует следующий код:

//destruct the objects in *pArr in the inverse order
//in which they were constructed
for (int i = the number of elements in the array - 1; i >= 0; --i)
{
     pArr[i].Base::~Base(); 
}

Теперь при этом мы получаем неопределенное поведение. Работа с массивами - это просто работа со смещениями, поэтому, когда этот цикл происходит, на каждой итерации цикла указатель массива увеличивается в соответствии с размером Base ->, и здесь все становится «неопределенным». В «простом» (но менее распространенном) случае, когда класс Derived не добавляет своих собственных членов, его размер равен размеру Base ->, поэтому все может (я думаю, что не всегда) работать хорошо. Но (!!), когда вы добавляете хотя бы один член в класс Derived, его размер увеличивается, что приводит к неправильному приращению смещения в каждой итерации.

Чтобы проиллюстрировать этот случай, я создал следующие базовые и производные объекты. Обратите внимание, что в случае, если Derived не содержит члена m_c , операция удаления проходит успешно (закомментируйте и убедитесь сами), YET, как только вы добавите его, я получил ошибка сегментации (которая является неопределенным поведением).

#include <iostream>
using namespace std;

class Base 
{

    public:
        Base(int a, int b)
        : m_a(a)
        , m_b(b)    
        {
           cout << "Base::Base - setting m_a:" << m_a << " m_b:" << m_b << endl;
        }

        virtual ~Base()
        {
            cout << "Base::~Base" << endl;
        }

        protected:
            int m_a;
            int m_b;
};


class Derived : public Base
{
    public:
    Derived() 
    : Base(1, 2) , m_c(3)   
    {

    }

    virtual ~Derived()
    {
        cout << "Derived::Derived" << endl;
    }

    private:    
    int m_c;
};

int main(int argc, char** argv)
{
    // create an array of Derived object and point them with a Base pointer
    Base* pArr = new Derived [3];

    // now go ahead and delete the array using the "usual" delete notation for an array
    delete [] pArr;

    return 0;
}
1 голос
/ 21 октября 2013
for(std::vector<MyObjectClass>::iterator beg = myVector->begin(), end = myVector->end(); beg != end; beg++)
{
    delete *beg;
}
myVector->clear();
0 голосов
/ 28 сентября 2010

Трудно сказать по вашему вопросу, что является подписью maps. Я предполагаю, что вы хотите использовать delete[], потому что вы также использовали new[]. Значит ли это, что члены вашего вектора сами по себе являются коллекцией? если это так, то у вас есть что-то вроде этого:

class Building {
  public:
    typedef int* maps_t;
  private:
    std::vector<maps_t> maps;
  public:
    Building();
    ~Building();
};

Building::Building(size_t num_maps) {
  for(;num_maps; --num_maps)
  {
    maps.push_back(new Building::maps_t[10]);  
  }
}

в этом случае ваш деструктор почти прав; вам нужно изменить только &maps[i] на maps[i].

Building::~Building() {
    int i;
    for (i=0; i<maps.size(); i++) {
        delete[] maps[i];
    }
}

Но в C ++ нам редко нравится так делать. С одной стороны, если вы на самом деле не пытаетесь реализовать что-то вроде std::vector, вы редко хотите явно использовать new[] или delete[]. Вы можете, например, использовать std::vector. В этом случае вам не нужно выполнять явное управление памятью. Ваш класс будет выглядеть так:

class Building {
  public:
    typedef std::vector<int> maps_t;
  private:
    std::vector<maps_t> maps;
  public:
    Building();
};

Building::Building(size_t num_maps) {
  for(;num_maps; --num_maps)
  {
    maps.push_back(Building::maps_t(10));  
  }
}

В этом случае пользовательский деструктор отсутствует, поскольку std::vector уже достаточно хорошо управляет собственной памятью.

0 голосов
/ 28 сентября 2010

Если вы используете std::vector, тогда вы можете просто позволить обработчику деструктором обработать vector, предполагая, что в указанных vector.

* есть "объекты" (а не указатели на объекты).1005 * - или -

Если вы используете стандартный массив в качестве "vector":

Назначение "delete []"вариант состоит в том, чтобы освободить весь массив и, следовательно, избежать необходимости иметь цикл for, как вы делаете.

Если вы используете стандартные массивы C / C ++, "delete [] maps" должен сделать это за вас.«[]» не должен использоваться для освобождения STL vector s.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...