Двойной бесплатный деструктор C++

Я пытаюсь реализовать связанный список на С++. Список содержит указатель на тип узла, выделенный в куче.

Код выглядит следующим образом:

#include <memory>

template<typename T>
class node {
public:
    node(T v) : value(v) {}
    
    ~node() = default;

    T value;
    node *next;
};

template<typename T, class Allocator = std::allocator<node<T>>>
class linked_list {
private:
    node<T>* head;
    Allocator alloc;

public:
    linked_list() : head(nullptr) {}

    ~linked_list() {
        for (auto start = head; start != nullptr; start = start->next) {
            start->~node();
            alloc.deallocate(start, 1);
        }
    }

    void push_back(T value) {
        node<T> *new_node = alloc.allocate(1); 
        new (new_node) node<T>(value);

        if (head == nullptr) {
            head = new_node;
            return;    
        }

        head->next = new_node;
        head = new_node;
    }
};

В main.cpp:

#include "linked_list.hpp"

int main() {
    linked_list<int> a;

    a.push_back(4);
    a.push_back(5);


    return 0;
}

Когда я запустил его, я обнаружил двойное освобождение в кеше T2. Что я сделал не так с деструктором?

Это start->~node(); очень странно. И зачем использовать новое размещение здесь new (new_node) node<T>(value); ? Я не думаю, что в этой программе требуется размещение new/delete.

Richard Critten 18.11.2022 15:38

Даже компилятор находит ошибку: godbolt.org/z/Kevjhenvo

Marek R 18.11.2022 15:38

Код нарушает правило 3/5/0: https://stackoverflow.com/questions/4172722/what-is-the-rule‌​-of-three

drescherjm 18.11.2022 15:40

Не могли бы вы уточнить?

Kain 18.11.2022 15:41

Я пытаюсь реализовать связанный список на С++. Почему вы запутались в использовании placement-new? Почему бы просто не использовать new, а затем перейти к умным указателям?

PaulMcKenzie 18.11.2022 15:44

Снова компилятор объясняет, в чем проблема: после одной итерации, когда объект был удален, start->next (в операторе for) есть доступ для перехода к следующему элементу, что такое UB!

Marek R 18.11.2022 15:44

@Kain Это цикл for (auto start = head; start!= nullptr; start = start->next) { start->~node(); alloc.deallocate (начало, 1); } вызывает неопределенное поведение.

Vlad from Moscow 18.11.2022 15:44

@PaulMcKenzie Я читал, что конструкция и уничтожение устарели в С++ 17 здесь: stackoverflow.com/questions/59539057/…

Kain 18.11.2022 15:45

@VladfromMoscow Не могли бы вы объяснить мне, почему это происходит?

Kain 18.11.2022 15:45

@PaulMcKenzie Интересно, была ли цель разрешить замену другого распределителя вместо стандартного. Это был синтаксис, используемый в C++98.

Spencer 18.11.2022 15:47

Когда вы вставляете новый узел, его указатель next остается неинициализированным. Как должно работать условие start != nullptr? Кроме того, head->next = new_node; head = new_node; не имеет для меня смысла. Куда указывает head->next после этих заданий?

Evg 18.11.2022 15:49
start = start->next поскольку start уже уничтожено, использование после уничтожения является неопределенным поведением.
Eljay 18.11.2022 15:49

Проблема не связана с правилом 3/5/0, так как ничего не копируется и не присваивается. Проблема в использовании после бесплатного!

Marek R 18.11.2022 15:50

Правило трех здесь даже отдаленно не применимо.

Spencer 18.11.2022 15:50

@Элджей, я понял, спасибо.

Kain 18.11.2022 15:53

Конструкция и уничтожение устарели в С++ 17. Это правда, но ответ, на который вы ссылаетесь, неверен. Правильный способ — пройти через std::allocator_traits, у которого есть статическая функция-член construct(). Смотрите здесь. Это также верно для allocate().

Evg 18.11.2022 16:03

Обратите внимание, что ваши node имеют только указатель на следующий элемент, и когда вы отправляете элемент в список, вы обновляете указатель head на новый элемент, что означает, что у вас нет способа вернуться к первому элементу.

Ranoiaetep 18.11.2022 16:14
Как настроить Tailwind CSS с React.js и Next.js?
Как настроить Tailwind CSS с React.js и Next.js?
Tailwind CSS - единственный фреймворк, который, как я убедился, масштабируется в больших командах. Он легко настраивается, адаптируется к любому...
LeetCode запись решения 2536. Увеличение подматриц на единицу
LeetCode запись решения 2536. Увеличение подматриц на единицу
Увеличение подматриц на единицу - LeetCode
Переключение светлых/темных тем
Переключение светлых/темных тем
В Microsoft Training - Guided Project - Build a simple website with web pages, CSS files and JavaScript files, мы объясняем, как CSS можно...
Отношения &quot;многие ко многим&quot; в Laravel с методами присоединения и отсоединения
Отношения &quot;многие ко многим&quot; в Laravel с методами присоединения и отсоединения
Отношения "многие ко многим" в Laravel могут быть немного сложными, но с помощью Eloquent ORM и его моделей мы можем сделать это с легкостью. В этой...
В PHP
В PHP
В большой кодовой базе с множеством различных компонентов классы, функции и константы могут иметь одинаковые имена. Это может привести к путанице и...
Карта дорог Беладжар PHP Laravel
Карта дорог Беладжар PHP Laravel
Laravel - это PHP-фреймворк, разработанный для облегчения разработки веб-приложений. Laravel предоставляет различные функции, упрощающие разработку...
0
17
88
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Это распространенная ошибка новичков. Вы изменили переменную управления циклом.

for (auto start = head; start != nullptr; start = start->next)
{
     start->~node();
     alloc.deallocate(start, 1);
}

Вы изменили start (удалив память) в теле цикла for, а затем попытались разыменовать указатель, который вы только что удалили, в его выражении продолжения. БУМ! Вам повезло, что библиотека времени выполнения была достаточно умна, чтобы уловить это и выдать вам ошибку «двойного освобождения», а не, скажем, запустить ядерную ракету.

Это одно из мест, где цикл while лучше, чем цикл for.

while (head)
{
   auto to_del = head;
   head = head->next; 
   to_del ->~node();
   alloc.deallocate(to_del, 1);
}

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

Есть и другие проблемы, например, push_back совершенно неправильно указывает, куда он вставляет новый узел. Замена head->next = new_node; на new_node->next = head;, по крайней мере, предохранит вашу программу от осиротения всех новых узлов.

Само по себе это не исправит код, потому что реализация push_back() также нарушена.

Evg 18.11.2022 16:24

@ Евг Согласен. Я немного отредактировал, но не хочу влезать в сорняки.

Spencer 18.11.2022 16:27
godbolt.org/z/coz5nvrY8
Marek R 18.11.2022 17:29

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