Двойной бесплатный деструктор 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
Стоит ли изучать 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
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

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