Деструктор связанного списка с помощью std::move C++

Я изучаю структуры данных в C++;

Я написал деструктор для связанного списка следующим образом:

    ~List(){
        Node* temporary = new Node;
        Node* node = head;
        while(node != nullptr){
            temporary = node;
            node = node->next;
            delete temporary;
        }
    }

Но потом я понял, что я могу сделать:

        ~List(){
        Node* node = head;
        while(node != nullptr){
            node = std::move(node->next);
        }
    }

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

std::move на указателе ни на что не влияет. Ясно, что вы что-то неправильно понимаете в объектной модели C++, но я не уверен, что это за недоразумение. Можете ли вы пояснить, почему вы думаете, что этот ход заменит delete? Во втором примере происходит утечка узла. Также странно new объект в деструкторе.
François Andrieux 22.11.2022 15:27

Я думаю, вы неправильно понимаете, как работают объекты в C++. Это не Java, я бы порекомендовал хорошую книгу по C++. std::move не звонит delete.

Quimby 22.11.2022 15:28

Если вы изучаете структуры данных, первое, что нужно усвоить (ИМХО), это то, что «связанный список» — это едва ли не худшая структура данных, которую вы можете использовать. Но если вы хотите настаивать на ужасном, то хотя бы не кодируйте сами, используйте std::list.

Jesper Juhl 22.11.2022 15:29

@François Andrieux Разве std::move не удаляет указатель после вызова?

Eric Cardozo 22.11.2022 15:30

Вам удалось внедрить - и пропустить - динамическое выделение в деструкторе. Определенно второе предложение прочитать хорошую книгу.

Useless 22.11.2022 15:30
Node* temporary = new Node; Значит, чтобы снести ряд домов, ты сначала идешь и строишь новый дом? Я не думаю, что бизнес по сносу, который занимается этим, долго не протянет.
n. m. 22.11.2022 15:30

@EricCardozo Нет, это не так. Указатели являются тривиальным фундаментальным типом, и их перемещение аналогично их копированию. Указатель просто содержит значение, которое говорит «какой объект». Это не тот объект, на который он указывает. Возможно, вы знакомы с языками высокого уровня, такими как Java или C#, где переменные обычно являются ссылками на экземпляр, управляющий временем жизни, но в C++ указатели работают иначе.

François Andrieux 22.11.2022 15:31

"Разве std::move не удаляет указатель после вызова?" - Нет. std::move - это не что иное, как ссылка на rvalue. Он ничего не делает сам по себе.

Jesper Juhl 22.11.2022 15:31

перемещение может быть разрушительным, но это только в том случае, если вы используете владеющие умными указателями или другими нетривиальными типами (и это происходит, когда вы используете ссылку rvalue в перемещении или присваивании, а не когда вы выполняете приведение)

Useless 22.11.2022 15:32

@Jesper Juhl Итак, все должны бросить свои специальности CS и бросить программирование, потому что все уже выдумано, вместо этого я должен искать работу по продаже автомобилей. Зачем учить С++? Могу ли я сделать это проще на питоне?

Eric Cardozo 22.11.2022 15:32

@EricCardozo Делай, что хочешь. Но я бы все же посоветовал против связанных списков. Кроме того, университет не выпускает таких замечательных настоящих программистов — их всегда приходится переучивать.

Jesper Juhl 22.11.2022 15:34

Нет ничего плохого в том, чтобы переписать существующие контейнеры для изучения IMO, но пока урок заключается в том, что вы еще не понимаете указатели.

Useless 22.11.2022 15:35

fwiw с std::unique_ptr<Node> ваш код будет работать.

apple apple 22.11.2022 15:50

«Я понял, что могу: [код] Избежать создания временного объекта» — более простой способ избежать создания «временного» объекта — не создавать его. (Если вы удалите = new Node из своего первого блока кода, вы избежите создания ненужного объекта, не влияя ни на что другое.)

JaMiT 22.11.2022 16:08
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
14
64
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Этот фрагмент кода

~List(){
    Node* temporary = new Node;
    Node* node = head;
    while(node != nullptr){
        temporary = node;
        node = node->next;
        delete temporary;
    }
}

производит утечку памяти из-за этого ненужного выделения памяти

    Node* temporary = new Node;

В этом фрагменте кода

    ~List(){
    Node* node = head;
    while(node != nullptr){
        node = std::move(node->next);
    }
}

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

Если вы не хотите использовать промежуточную переменную, то деструктор можно написать, например, следующим образом

    #include <functional>

    //...

    ~List(){
        while ( head ) delete std::exchange( head, head->next );
    }
}

Почему ручное управление памятью, а не умные указатели?

Jesper Juhl 22.11.2022 15:39

@JesperJuhl Где вы нашли умные указатели в исходном вопросе? Очевидно, что автор вопроса учится управлять памятью с помощью new и delete.

Vlad from Moscow 22.11.2022 15:42

И удалить дальше деструктор Node? если вы решили реализовать односвязный список, я не уверен, что рекурсивные деструкторы — это улучшение.

Useless 22.11.2022 15:43

@Useless Нет рекурсивных разрушений. Используется цикл while.

Vlad from Moscow 22.11.2022 15:44

@JesperJuhl есть несколько идей структуры данных хуже, чем у связанного списка. Один из них — связанный список с умными указателями.

n. m. 22.11.2022 15:46

Нет рекурсивных уничтожений, потому что вы не используете интеллектуальные указатели, владеющие узлом.

Useless 22.11.2022 16:12
Ответ принят как подходящий

std::move ничего не делает сам по себе, он только приводит что-то к rvalue.

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

Но, например, если вы используете std::unique_ptr, оператор =(unique_ptr&&) удалит исходные данные после присваивания*.


поэтому, если вы используете что-то вроде

#include <memory>

struct Node{
   std::unique_ptr<Node> next;
   // possibly move List destructor here
   // i.e. destruct a Node would safely remove all sub-node non-recursively
};

struct List{
   std::unique_ptr<Node> head;
   // write destructor to prevent deep recursion
   ~List(){
      while(head) head = std::move(head->next); // current head is deleted after assignment
   }
};

тогда это сработает


*btw, self-assignment is safe because it's actually effectively reset(r.release())

Другие вопросы по теме