нарушение прав записи this-> head было 0xDDDDDDDD в pop_front - PullRequest
0 голосов
/ 15 октября 2019

У меня есть некоторый код связанного списка, и все работает, пока я не создаю свою функцию pop_front в моем деструкторе. Код работает везде, и я попытался распечатать связанный список, чтобы увидеть, был ли он создан правильно и был. Это единственная часть моего кода, где вызывается pop_front, и единственная часть, где я освобождаю что-либо

template <typename T>
class DList {
    Node<T>* head;
    Node<T>* tail;
public:
    DList() {
        head = nullptr;
        tail = nullptr;
    }

    void push_front(T newData) {
        Node<T>* newNode = new Node<T>(newData, head, nullptr);
        if (head) {
            head->prev = newNode;
        }
        else {
            tail = newNode;
        };
        head = newNode;
    }
    void push_front(DList<T> newList) {

        newList.tail->next = head;
        head->prev = newList.tail;
        head = newList.head;

    }
    void pop_front() {
        if (head) {
            Node<T>* pop = head;
            head = pop->next;
            if (head) {
                head->prev = nullptr;
            }
            else {
                tail = nullptr;
            }
            delete pop;
        }
    }
    ~DList() {
        while (head) {
            pop_front();
        }
    }
};

1 Ответ

2 голосов
/ 15 октября 2019

Ваш DList класс (и, возможно, также шаблонный класс Node, мы не видим его определения) не следует правилу трех / пяти . Вы определяете деструктор, но не определяете конструктор копирования или оператор присваивания.

Это означает, что создание копии DList вообще (что вызов DList::push_front(DList<T>) почти наверняка вызовет) вызоветuse-after-free в деструкторе.

Помните, что сгенерированные компилятором конструкторы копий и операторы присваивания копий просто выполняют членскую копию значений. В этом случае ваши значения являются указателями. Следовательно, копия объекта DList просто скопирует исходные указатели на новый объект.

Когда один из этих объектов будет уничтожен, список будет снесен, однако другой объект будет уничтоженпо-прежнему сохраняются указатели на выделенные области памяти, которые были освобождены.

Вы должны реализовать конструктор копирования и оператор назначения копирования, если вы хотите, чтобы ваш тип был копируемым, потому что компиляторсгенерированные версии будут делать неправильные вещи. Если вы используете C ++ 11, вы должны также определить конструктор перемещения и оператор присваивания перемещения.

В C ++ 11 вы также можете удалить эти конструкторы и операторы, и тогда вы получитеВременная ошибка везде, где вы пытаетесь сделать копию:

DList(DList const &) = delete;
DList(DList &&) = delete;

DList & operator=(DList const &) = delete;
DList & operator=(DList &&) = delete;

Без определения класса шаблона Node я не могу предложить реализацию конструктора копирования или оператора назначения копирования. Однако я могу предложить вариант реализации вариантов перемещения:

void swap(DList & other) {
    std::swap(head, other.head);
    std::swap(tail, other.tail);
}

DList(DList && other) : head(nullptr), tail(nullptr) {
    swap(other);
}

DList & operator=(DList && other) {
    swap(other);
    return *this;
}

Еще одна ошибка:

void push_front(DList<T> newList)

Должно быть:

void push_front(DList<T> & newList)

Поскольку вы изменяете «новый список» - нет смысла изменять копию. Однако семантическое значение этого не совпадает push_front(T)!

Перегрузка с одним значением принимает значение и помещает его в этот список.

списокПерегрузка принимает другой список и помещает этот список в список other . Было бы гораздо разумнее, если бы перегрузка списка помещала список other в список this , поскольку это отражало бы поведение перегрузки с одним значением ("добавьте эту вещь в этот список, что бы это ни значило ").

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

...