Бесконечный цикл двусвязного списка?

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

class Node
{
    Node(  );

    ~Node(  )
    {
       delete mNext; //deallocs next node
    }

    Contact mContact;
    Node* mPrevious;
    Node* mNext;
}; 

Изменить: если бы я изменил код, это сработало бы?

~Node(  )
{
   mPrevious = NULL;
   if (mNext->mPrevious != NULL)
   {
      delete mNext; //deallocs next node
   }
}

Редактировать 2: Или это будет работать лучше всего?

~Node(  )
{
   if (mPrevious != NULL)
   {
      mPrevious = NULL;
      delete mNext; //deallocs next node
   }
}

person Connor Martin    schedule 25.09.2011    source источник
comment
Я не уверен, почему вы думаете, что это сформирует бесконечный цикл, вы должны просто проверить, не является ли mNext NULL, прежде чем удалять его.   -  person Nican    schedule 25.09.2011
comment
произойдет сбой, поскольку mNext не был установлен. Также вы можете удалить что-то дважды, если дважды добавите это в список, чего не следует делать. также никогда не используйте список ссылок   -  person    schedule 25.09.2011
comment
Я не уверен, почему вы кодируете свой собственный двусвязный список, когда в STL есть множество структур списков, которые можно взять. (Если это не домашнее задание или какое-либо другое учебное упражнение, то есть...)   -  person Ryan Ballantyne    schedule 25.09.2011
comment
не очень хорошая идея, imo, потому что очень длинные списки могут вызвать переполнение стека.   -  person Nick Dandoulakis    schedule 25.09.2011
comment
@Nican: На самом деле НЕТ, ВЫ НЕ ДОЛЖНЫ ПРОВЕРЯТЬ, ЕСЛИ mNext IS NULL. delete будет игнорировать его, если он равен нулю. Одна из моих любимых мозолей — это проверка кода на наличие нулевого значения перед удалением.   -  person    schedule 25.09.2011
comment
@acidzombie24: О, спасибо. Не знал этого.   -  person Nican    schedule 25.09.2011
comment
Черт, нет. Не делай этого! Если вы не хотите, чтобы список был РАЗРЕШЕН быть в круге. Мне просто нужно что-то добавить/удалить mContact, и он просто добавит/удалит контакты. Если вы хотите, чтобы первая ссылка была на конец, вам нужно больше подумать. Можно ли случайно добавить ссылку дважды (например, в середине и в конце). Также кажется, что вы не тестируете какой-либо код. и я предполагаю, что это для обучения? Если вас не заставляют использовать это, изучите stl   -  person    schedule 25.09.2011
comment
К вашему сведению, если первое и последнее соединение, вы бы удалили, например, delete root. Тогда деструктору не нужно проверять, является ли mPrevious нулевым, потому что, вероятно, он будет указывать на себя, когда его один элемент (или нулевой, тогда вам нужно проверить). Затем он должен сделать mPrevious->mNext=0;, так как mPrevious->mNext должна быть текущей ссылкой, и вы уже удаляете ее (вы должны быть, если она находится в деструкторе). Затем, когда он дойдет до последнего, он удалит null, и ничего не произойдет, что не приведет к бесконечному циклу деструктора.   -  person    schedule 25.09.2011
comment
Я просто хочу кое-что уточнить - речь идет о кольцевом двусвязном списке, где первый и последний элементы связаны между собой, или просто об обычном, который начинается и заканчивается NULL? @acidzombie24 Я не согласен, я думаю, что важно знать основы того, как работают простые структуры данных, и особенно связанные списки - это позже даст вам представление о других структурах данных, сложности различных операций и т. д.   -  person Eran Zimmerman Gonen    schedule 25.09.2011
comment
@Eran: я вроде согласен. Я не думаю, что мне когда-либо приходилось писать контейнер (я написал два и выбросил их, они были пустой тратой времени. один был глупым, который был запирающим деревом, которое блокируется каждый раз, когда вы получаете доступ к чему-либо, а другой был deque, прежде чем я услышал стл). На самом деле все, что вам нужно знать, это время поиска и особенности контейнера (например, можете ли вы их объединить или это вектор, что означает, что вы можете использовать его как char *).   -  person    schedule 25.09.2011
comment
Уточнение: вы ожидаете, что цикл будет а) содержать один и тот же объект более одного раза или б) будет круговым? Они существенно меняют ответ.   -  person Steve Rowe    schedule 25.09.2011


Ответы (5)


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

То, что, вероятно, произойдет,

  1. Выдается первый "внешний" delete node;.
  2. При входе в деструктор узла еще ничего не делается, так как деструктор кода является «первой» вещью, выполняемой в процессе уничтожения (сам процесс уничтожения весьма сложен и включает в себя код деструктора, изменение класса, уничтожение члена, уничтожение базы в следующем порядке: см. этот ответ для более подробного объяснения ).
  3. Первая инструкция заполнения деструктора выполняет delete mNext;, тем самым запуская тот же процесс на следующем узле в цикле.
  4. Поскольку узлы образуют петлю, эта цепочка снова достигнет node «сзади», что сделает самый первый вызов рекурсией, которая никогда не закончится.
  5. Тем не менее каждый вызов будет выделять место в стеке для записи активации, поэтому через некоторое время вся память, разрешенная для использования для стека, будет исчерпана, и ОС уничтожит процесс. Вызов удаления не является «хвостовым вызовом», потому что после завершения кода деструктора память должна быть освобождена, поэтому эту рекурсию нельзя легко оптимизировать... в то время как delete mNext; является последним оператором в деструкторе, все же есть операции, которые должны быть выполнены после оператор delete завершает работу.

Однако обратите внимание, что, по моему опыту, переполнение стека, если вы не используете специальные параметры компилятора, не будет проверяться, и поэтому завершение программы будет довольно «ненормальным». Также обратите внимание, что в Windows есть какой-то ужасный код, который в некоторых случаях скрывает ошибки segfault, если они происходят при завершении программы, поэтому вполне возможно, что программа Windows может просто изящно завершаться в этой операции, выполняемой после выхода из цикла событий.

Учтите, что переполнение стека обычно не рассматривается, на самом деле возможно любое поведение, включая очевидный «бесконечный цикл» (обратите внимание, что этот бесконечный цикл может быть не одним из рекурсивных деструкторов, а где-то внутри системы выполнения, которая сходит с ума из-за переполнения стека) .

Почему я использовал слово вероятно? Причина в том, что стандарт C++ говорит, что многократное уничтожение объекта является неопределенным поведением. Если вы добавите это к тому факту, что в C++ нет способа выйти из деструктора без завершения уничтожения, вы поймете, что компилятору теоретически разрешено помечать объект как «уничтожаемый» и заставлять демона вылетать из вашего nosrils, если дважды ввести деструктор одного и того же объекта. Однако проверка этой ошибки не является обязательной, и составители компиляторов часто ленивы (это НЕ оскорбление для программиста), и поэтому маловероятно, что эта проверка будет присутствовать (за исключением случаев, когда включена какая-то специальная дополнительная опция отладки).

Подводя итог: может ли это зацикливаться вечно? да. Может ли это рухнуть? Конечно. Может ли он перестать говорить мне, что объект уничтожается дважды? конечно. Может ли он просто красиво завершить программу (т.е. без установки кода ошибки)? да, это тоже.

Все может случиться. И Мерфи говорит, что произойдет то, что нанесет вам наибольший ущерб... например, программа будет завершаться каждый раз, пока вы ее разрабатываете... перед тысячей потенциальных клиентов.

Только не делай этого :-)

person 6502    schedule 25.09.2011

У него нет способа узнать, когда остановиться, поэтому он, вероятно, будет работать бесконечно.
Возможно, вам следует написать класс List, который имеет указатель на (или фактический) Node. d'tor Node должен заботиться только о своих собственных полях, в данном случае mContact. Д'тор List должен перебирать все узлы в списке (запоминая, когда нужно остановиться) и удалять каждый из них (ровно один раз).

person Eran Zimmerman Gonen    schedule 25.09.2011
comment
На самом деле он не будет работать бесконечно. Это приведет к сбою, если mNext недействителен, или остановится, когда оно равно нулю. - person ; 25.09.2011
comment
@acidzombie24 Acidzombie24 Но, если предположить, что список в первую очередь является законным, d'tor следующего узла должен быть вызван до того, как какое-либо пространство будет освобождено. А так как этот d'tor вызывает следующий и т.д., то получается бесконечный пробег. - person Eran Zimmerman Gonen; 25.09.2011
comment
хммм отличная точка на заказ dtor. +1 :D - person ; 25.09.2011
comment
@Eran, в какой-то момент mNext будет нулевым, и тогда цепочка удалений раскрутится. Я до сих пор не понимаю, почему он может быть бесконечным. Если бы список был достаточно длинным, возможно, у вас были бы проблемы со стеком, но не с бесконечностью. - person Steve Rowe; 25.09.2011
comment
@SteveRowe Плохо, я предположил, что это циклический двусвязный список, в котором последний и первый элементы связаны между собой. - person Eran Zimmerman Gonen; 25.09.2011
comment
@Эран, а. Да, в циклическом списке он стал бы бесконечным. Для кругового списка будет полезен предложенный вами контейнер. - person Steve Rowe; 25.09.2011

Предполагая, что вы инициализируете mNext значением null, он не будет работать бесконечно. Удалить ничего не сделает, когда встретит нулевой указатель. Таким образом, это закончится именно тогда, когда вы этого ожидаете.

Я не уверен, что вы делаете с параметрами «если раньше». Это не сработает. Либо это будет действительный узел и, следовательно, будет иметь предыдущий узел, либо он не будет действительным узлом, и проверка предыдущего приведет к неопределенным результатам. Придерживайтесь простого ответа:

class Node 
{ 
Node(  mNext = NULL; ); 

~Node(  ) 
{ 
   delete mNext; //deallocs next node 
} 

Contact mContact; 
Node* mPrevious; 
Node* mNext; 
};  

Уточнение: это решение работает, но только при соблюдении двух условий: 1) Нет узлов, появляющихся в списке дважды. 2) Список не круговой. Если вы можете гарантировать эти условия, это ваш самый простой ответ. Если вы не можете, вам нужно сделать что-то более сложное.

person Steve Rowe    schedule 25.09.2011

Лично я считаю немного странным, что деструктор Node должен иметь какое-то отношение к другим узлам.

Если бы дизайн зависел от меня, я бы создал класс List, содержащий указатель на Node объекты (first и last). Деструктор класса List позаботится об итерации по всем узлам в списке и их уничтожении.

person mpenkov    schedule 25.09.2011

Это на самом деле просто. Предположения 1) Это список с двойными ссылками, а не круговой 2) Нет циклов в списке ссылок: это список с двойными ссылками 3) Класс реализации имеет только один экземпляр Node Вероятно, называется HeadNode или LinkList;) и это узел, который явно уничтожается


Пример: LinkList состоит из 1->2->3->4->5->6->NULL. Вызов диструктора для HeadNode(ссылка на 3-е предположение) вызовет рекурсивный вызов следующим образом: delete(1)->delete(2 )->delete(3)->delete(4)->delete(5)->delete(6)->NULL Итак, пожалуйста, проверьте, если (mNext!= NULL) удалите mNext, и это работает :)


Но: если вы хотите удалить узел конкретно: скажем, мы хотим удалить только 4 в приведенном выше примере, все узлы будут удалены до NULL, поэтому перед удалением убедитесь, что вы установили Mnext в NULL.


Лучшей практикой было бы использовать библиотеку STL или иным образом использовать класс автоуказателя для части уничтожения проблемы.

person vivek sapru    schedule 25.09.2011