Как освободить память динамического массива структур

Как человек, никогда не занимавшийся освобождением памяти и прочим, передо мной встала задача создать динамический массив структур и создать функции для добавления или удаления элементов массива. При удалении я должен освободить память, которая больше не нужна.

при удалении 2-го элемента массива размером 3 я перемещаю 3-й элемент на 2-ю позицию, а затем удаляю последний. При удалении последнего всегда выдает ошибку... Кто-нибудь может найти решение для меня?

struct myFriend {
    myFriend() {
        number=0;
        hobbys = new char*[10];
    }
    int number;
    char* name;
    char** hobbys;
};
int main() {
    myFriend* friendList = new myFriend[10];

    myFriend* tempFriend = new myFriend;
    tempFriend->number=1;
    tempFriend->name = "ABC";

    myFriend* tempFriend2 = new myFriend;
    tempFriend2->number=2;
    tempFriend->name = "XYZ";

    myFriend* tempFriend3 = new myFriend;
    tempFriend3->number=3;
    tempFriend3->name = "123";

    friendList[0] = *tempFriend;
    friendList[1] = *tempFriend2;
    friendList[2] = *tempFriend3;

    friendList[1] = friendList[2]; //move 3rd element on 2nd position
    delete &(friendList[2]); //and delete 3rd element to free memory
}

person Sebastian    schedule 22.04.2011    source источник
comment
Вместо этого вы должны использовать std::vector и std::string.   -  person sharptooth    schedule 22.04.2011
comment
Говорят, что для задачи используются массивы символов. Но, может быть, это изменится. Тем не менее, это не решение реальной проблемы :( ;)   -  person Sebastian    schedule 22.04.2011
comment
Вам придется отслеживать каждый указатель, возвращенный из new и delete ровно один раз. Вы также должны отслеживать каждый указатель, возвращенный из new [], и использовать delete[] для этого указателя ровно один раз. Угадайте, почему нам больше нравится вектор и строка!   -  person Bo Persson    schedule 22.04.2011


Ответы (6)


Зачем вы создали временные переменные? Они даже не нужны.

Если вы используете std::vector и std::string, проблема, с которой вы столкнулись, исчезнет автоматически:

std::vector<myFriend> friendList(10);

friendList[0]->number=1;
friendList[0]->name = "ABC";

friendList[1]->number=2;
friendList[1]->name = "XYZ";

friendList[2]->number=3;
friendList[2]->name = "123";

Чтобы заставить его работать, вы должны переопределить свою структуру как:

struct myFriend {
    int number;
    std::string name;
    std::vector<std::string> hobbys;
};

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

struct Friend 
{
    int    number;
    char*  name;
};

Friend * friends = new Friend[3];

friends[0]->number=1;
friends[0]->name = new char[4];
strcpy(friends[0]->name, "ABC");

//similarly for other : friends[1] and friends[2]

//this is how you should be deleting the allocated memory.
delete [] friends[0]->name;
delete [] friends[1]->name;
delete [] friends[2]->name;

delete [] friends; //and finally this!

И если вы сделаете что-либо из следующего, это будет неправильно и вызовет неопределенное поведение:

delete friends[2];    //wrong
delete &(friends[2]); //wrong
person Nawaz    schedule 22.04.2011
comment
Проблема в том, что нужно научиться работать с массивами. Возможно, разрешено использовать строки, но, к сожалению, нет возможности работать с векторами. Должно быть какое-то другое решение, я думаю. - person Sebastian; 22.04.2011
comment
@Себастьян: Хорошо. Затем позвольте объяснить, как вы должны работать с указателями. - person Nawaz; 22.04.2011
comment
@Nawaz: я знаю, как работать с указателями. Я просто не знаю, как удалить элемент массива. Я просто могу удалить весь массив, но это не главное. И нам приходится работать с такими массивами. Динамические массивы (определяющие следующий и/или предыдущий элемент в структуре) также не допускаются — это было бы довольно просто. - person Sebastian; 22.04.2011
comment
@Blazes: Упс. да... позвольте мне исправить это. - person Nawaz; 22.04.2011

Невозможно удалить подмножество из массива, выделенного new []

myFriend* friendList = new myFriend[10];

У вас есть один целый массив

+------------------------------------------------------------------+
|  friendList[0]  |  friendList[1]  |    .....  |   friendList[9]  | 
+------------------------------------------------------------------+

Вы не можете delete &(friendList[2]). Вы получаете из C++ целый массив из 10 элементов. Этот массив начинается с friendList (или &(friendList[0])).

operator delete с указателем на адрес, возвращаемый new (т.е. friendList), допустим только.

person Alexey Malistov    schedule 22.04.2011
comment
Вы на месте. Ему нужен массив указателей. Я разместил решение выше, но я отметил это как полезный момент. - person Blazes; 22.04.2011

Я заметил две вещи. (1) Очевидно, вы должны «создавать функции для добавления или удаления элементов», но вы этого не сделали, вы создали только одну функцию. (2) Вы усложняете свою работу, используя структуру, которая также должна управлять памятью. Я предлагаю вам использовать более простую структуру.

Ваше задание, по сути, состоит в том, чтобы создать простой «векторный» класс, поэтому я предлагаю вам это сделать. Начните с пустой структуры. Если учитель требует, чтобы вы использовали структуру myFriend в том виде, в каком она написана, вы можете добавить ее в после завершения создания векторных функций. Я предполагаю, что вам еще не разрешено проводить занятия, потому что большинство инструкторов совершают ошибку, оставляя это напоследок.

struct MyStruct {
    int value; // start with just one value here. Dealing with pointers is more advanced.
};

MyStruct* array;
int size;
int capacity;

void addMyStruct(MyStruct& value); // adds a MyStruct object to the end.
void removeMyStructAtPosition(int position); // removes the MyStruct object that is at 'position'

// I leave the functions for you to implement, it's your homework after all, but I give some clues below.

void addMyStruct(MyStruct& value) {
    // First check that there is enough capacity in your array to hold the new value. 
    // If not, then make a bigger array, and copy all the contents of the old array to the new one.
    // (The first time through, you will also have to create the array.)
    // Next assign the new value to array[size]; and increment size
}

void removeMyStructAtPosition(int position) {
    // If the position is at end (size - 1,) then simply decrement size.
    // Otherwise you have to push all the structs one to the left (array[i] = array[i + 1])
    // from position to the end of the array.
}

int main() {
    // test your new class here.
    // don't forget to delete or delete [] any memory that you newed.
}
person Daniel T.    schedule 22.04.2011

Размер массива фиксирован и равен 10, поэтому вам не нужно удалять из него какие-либо элементы. Но вам нужно удалить элементы name и hobbys из friendList[1]перед его перезаписью). Здесь есть две проблемы:

  1. Вы устанавливаете friendList[0]->name = "ABC"; Здесь "ABC" — это постоянная строка с нулевым символом в конце где-то в памяти. Вам не разрешено удалять его. Так что придется делать копию.
  2. Вы хотите удалить hobby[i] всякий раз, когда он был назначен. Но в вашем коде вы не можете сказать, был ли он назначен. Таким образом, вы должны установить каждый элемент в 0 в конструкторе, чтобы позже вы знали, какие элементы удалить.

Надлежащее место для удаления этих элементов находится в деструкторе myFriends.

person TonyK    schedule 22.04.2011
comment
‹‹Здесь ABC — это постоянная строка с нулевым завершением где-то в памяти. Вы не можете его удалить.›› Где в коде автор пытается его удалить? - person Serge Dundich; 22.04.2011
comment
Ну, это то, что ОП хочет добавить в свой код: удаление неиспользуемой памяти. Так что в общем случае элемент name придется удалить. Но в этом есть ловушка, на которую я заранее указывал. - person TonyK; 22.04.2011

Кажется, смысл вопроса в том, чтобы управлять динамическим массивом. Основная проблема в том, что он использует массив FriendList. Используйте массив указателей на friendList:

struct myFriend {
    myFriend() {
        number=0;
        hobbys = new char*[10];
    }
    int number;
    char* name;
    char** hobbys;
};
int main() {
    myFriend** friendList = new myFriend*[10];

    myFriend* tempFriend = new myFriend;
    tempFriend->number=1;
    tempFriend->name = "ABC";

    myFriend* tempFriend2 = new myFriend;
    tempFriend2->number=2;
    tempFriend->name = "XYZ";

    myFriend* tempFriend3 = new myFriend;
    tempFriend3->number=3;
    tempFriend3->name = "123";

    friendList[0] = tempFriend;
    friendList[1] = tempFriend2;
    friendList[2] = tempFriend3;

    friendList[1] = friendList[2]; //move 3rd element on 2nd position
    delete friendList[2]; //and delete 3rd element to free memory
}

Но все остальные правы — есть серьезные проблемы с выделением памяти как для «хобби», так и для «имени», которые нужно решать отдельно.

person Blazes    schedule 22.04.2011

В качестве домашнего задания я бы предложил узнать больше об указателях, операторах new/delete, операторах new[]/delete[] (не путать с операторами new/delete) и создании/копировании/конструкторах/деструкторах объектов. Это базовые возможности C++, и ваша задача как раз об этом.

Чтобы указать некоторые направления:

1) Когда вы динамически выделяете объект, подобный этому

MyType* p = new MyType;

or

MyType* p = new MyType(constructor_parameters);

вы получаете указатель p на созданный объект (new выделяет память для одного объекта типа MyType и вызывает конструктор этого объекта).

После того, как ваша работа с этим объектом завершена, вы должны позвонить

delete p;

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

2) Когда вы динамически выделяете массив, подобный этому

MyType* p = new MyType[n];

вы получаете указатель p на массив n созданных объектов, расположенных последовательно в памяти (new[] выделяет одиночный блок памяти для n объектов типа MyType и вызывает конструкторы по умолчанию для каждого объекта).

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

delete[] p;  // not "delete p;"

delete[] вызывает деструктор каждого объекта в массиве, а затем освобождает память. Если вы не позвоните delete[], ваша память будет утеряна. Если вы вызываете его более одного раза, поведение не определено (вероятно, сбой программы). Если вы вызываете delete вместо delete[], поведение не определено (вероятно, деструктор вызывается только для первого объекта, а затем пытается освободить блок памяти - но может быть что угодно).

3) Когда вы назначаете структуру/класс, вызывается operator=. Если у вас нет operator=, явно определенного для вашей структуры/класса, то генерируется неявный оператор = (он выполняет назначение каждого нестатического члена вашей структуры/класса).

person Serge Dundich    schedule 22.04.2011