C++析构函数被意外调用双链表
C++ destructor being called unexpectedly doubly linked list
doubly_linked_list::~doubly_linked_list()
{
list_item* current = head;
while (current)
{
list_item* next = current->Get_Next();
delete current;
current = next;
}
}
我正在C++编写一个动态图,使用双向链表来保存 Graph 节点,但是我的代码不断失败,因为它调用析构函数的次数超过了它应该调用的次数,例如,在我的图析构函数期间
Dynamic_Graph::~Dynamic_Graph()
{
edges.~doubly_linked_list();
nodes.~doubly_linked_list();
}
双向链表的析构函数被调用了 2 次以上,尽管它被显式调用了两次。
然后我有插入边缘的功能:
Graph_Edge* Dynamic_Graph::Insert_Edge(Graph_Node* from, Graph_Node* to)
{
Graph_Edge* new_edge = new Graph_Edge(from, to);
from->Get_out_Nodes().List_Insert_After(to, from->Get_out_Nodes().Get_Tail());
to->Get_in_Nodes().List_Insert_After(from, to->Get_in_Nodes().Get_Tail());
edges.List_Insert(new_edge);
return new_edge;
}
将 To 节点添加到 from 节点的邻接列表后,代码无法解释地调用双链表的析构函数并删除此插入,但我不确定为什么。
以下是图表和链表的标题:
class doubly_linked_list
{
private:
list_item* head;
list_item* tail;
public:
doubly_linked_list() { this->head = NULL; this->tail = NULL; }
doubly_linked_list(list_item* head){ this->head = head; this->tail = NULL; }
~doubly_linked_list();
list_item* Get_Head() { return head; }
list_item* Get_Tail() { return tail; }
void Set_Head(list_item* h) { head = h; }
void List_Insert(list_item* x);
void List_Insert_After(list_item* x, list_item* y);
void List_Delete(list_item* x);
};
class Dynamic_Graph
{
protected:
doubly_linked_list edges;
doubly_linked_list nodes;
public:
Dynamic_Graph() { edges = doubly_linked_list(); nodes = doubly_linked_list(); }
~Dynamic_Graph();
Graph_Node* Insert_Node(unsigned node_key);
void Delete_Node(Graph_Node* node);
Graph_Edge* Insert_Edge(Graph_Node* from, Graph_Node* to);
void Delete_Edge(Graph_Edge* edge);
Rooted_Tree* SCC() const;
Rooted_Tree* BFS(Graph_Node* source) const;
};
欢迎任何帮助
编辑: 我删除了对析构函数的调用,但是我仍然在插入函数中收到析构函数调用,我不确定为什么。
编辑2: 添加更多相关的代码行:
Graph_Edge* Dynamic_Graph::Insert_Edge(Graph_Node* from, Graph_Node* to)
{
Graph_Edge* new_edge = new Graph_Edge(from, to);
from->Get_out_Nodes().List_Insert_After(to, from->Get_out_Nodes().Get_Tail());
to->Get_in_Nodes().List_Insert_After(from, to->Get_in_Nodes().Get_Tail());
edges.List_Insert(new_edge);
return new_edge;
}
这是触发错误的功能,尽管它不应该被删除,但它访问已删除的指针,它在完成将"to"节点插入"发件人"节点邻接列表后触发。
void doubly_linked_list::List_Insert_After(list_item* x, list_item* y)
{
if (!y)
{
head = x;
tail = x;
return;
}
if (y == tail)
{
tail = x;
}
if (y->Get_Next())
{
y->Get_Next()->Set_Prev(x);
}
x->Set_Next(y->Get_Next());
x->Set_Prev(y);
y->Set_Next(x);
}
此函数是双向链表中的插入后函数。
编辑3: 我试图通过尽可能少的功能来解决这个问题,这就是我得到的:
#pragma once
#include < cstddef >
class List_Item
{
protected:
List_Item* next;
List_Item* prev;
public:
List_Item(): next(NULL), prev(NULL) {}
List_Item* Get_Next() { return next; }
List_Item* Get_Prev() { return prev; }
void Set_Next(List_Item* next) { this->next = next; }
void Set_Prev(List_Item* prev) { this->prev = prev; }
List_Item(const List_Item& item) : next(item.next), prev(item.prev){}
List_Item& operator=(const List_Item& item) { this->next = item.next; this->prev = item.prev; return *this; }
};
class Doubly_Linked_List
{
protected:
List_Item* head;
List_Item* tail;
public:
Doubly_Linked_List() : head(NULL), tail(NULL) {}
~Doubly_Linked_List();
List_Item* Get_Head() { return head; }
List_Item* Get_Tail() { return tail; }
void Set_Head(List_Item* h) { head = h; }
void Set_Tail(List_Item* t) { tail = t; }
void insert(List_Item* item);
void insert_after(List_Item* item, List_Item* after);
Doubly_Linked_List(const Doubly_Linked_List& list) : head(list.head), tail(list.tail) {}
Doubly_Linked_List& operator=(const Doubly_Linked_List& list) { this->head = list.head; this->tail = list.tail; return *this; }
};
Doubly_Linked_List::~Doubly_Linked_List()
{
List_Item* item = head;
while (item)
{
List_Item* next = item->Get_Next();
delete item;
item = next;
}
}
void Doubly_Linked_List::insert(List_Item* item)
{
item->Set_Next(head);
if (head)
head->Set_Prev(item);
if (!head)
tail = item;
head = item;
item->Set_Prev(NULL);
}
void Doubly_Linked_List::insert_after(List_Item* item, List_Item* after)
{
if (!after)
{
head = item;
tail = item;
return;
}
if (after->Get_Next())
{
after->Get_Next()->Set_Prev(item);
}
else
{
tail = item;
}
item->Set_Next(after);
item->Set_Prev(after);
after->Set_Next(item);
}
#pragma once
#include "List_Item.h"
#include"Doubly_Linked_List.h"
class Graph_Node :
public List_Item
{
protected:
const unsigned key;
Doubly_Linked_List in_nodes;
Doubly_Linked_List out_nodes;
public:
Graph_Node(unsigned key): key(key) {}
Doubly_Linked_List Get_In_Nodes() { return in_nodes; }
Doubly_Linked_List Get_Out_Nodes() { return out_nodes; }
};
class Graph_Edge :
public List_Item
{
protected:
Graph_Node* from;
Graph_Node* to;
public:
Graph_Edge(Graph_Node* f, Graph_Node* t): from(f), to(t) {}
Graph_Node* Get_From() { return from; }
Graph_Node* Get_To() { return to; }
};
int main()
{
unsigned node_key = 1;
Graph_Node* from = new Graph_Node(node_key++);
Graph_Node* to = new Graph_Node(node_key++);
from->Get_Out_Nodes().insert_after(to, from->Get_Out_Nodes().Get_Tail());
to->Get_In_Nodes().insert_after(from, to->Get_In_Nodes().Get_Tail());
}
您不应该手动调用析构函数。它们会自动为您调用。
您只需要使用delete
(它也调用析构函数(,并且仅在您实际通过调用new
创建的对象上使用。当到达其范围的尽头时,将自动销毁所有其他对象,然后将自动调用其析构函数。对于类的成员也是如此。
这里唯一的例外是,如果您使用放置-new来创建对象,或者如果您打算重复使用其存储。但你可能不打算做这样的事情。
完全删除Dynamic_Graph
的析构函数。不需要。
您的类doubly_linked_list
违反了 0/3/5 规则。该规则说,如果您定义自定义析构函数,则还应定义自定义 copy(/move( 构造函数和 copy(/move( 赋值运算符。
如果您违反了此规则,并且碰巧创建了该类对象的隐式或显式副本,则可能会有未定义的行为,因为析构函数将被调用两次并尝试删除内存两次。
问题后使用完整示例进行编辑:
在您现在显示的代码中,Doubly_Linked_List
的析构函数被调用了四次(参见 https://godbolt.org/z/KmStwy(。这些调用是因为Get_In_Nodes
和Get_Out_Nodes
返回Doubly_Linked_List
列表按值作为Graph_Node
类成员的副本。由于您在main
中调用这些函数四次,因此需要销毁四个Doubly_Linked_List
临时对象(编译器会自动执行(。
您没有正确实现Doubly_Linked_List(const Doubly_Linked_List& list)
(和复制赋值运算符(来实际复制整个列表,因此您的程序仍然具有未定义的行为。您需要以深度复制整个列表的方式实现复制操作,否则原始节点和副本的析构函数调用将delete
相同的节点。
或者,您可以将复制操作定义为已删除:
Doubly_Linked_List(const Doubly_Linked_List& list) : head(list.head), tail(list.tail) = delete
Doubly_Linked_List& operator=(const Doubly_Linked_List& list) = delete;
在这种情况下,编译器将不允许复制,并在您尝试复制时给出编译时错误。但是您当前的实现和以前一样破碎。您基本上只是复制了隐式复制操作的作用。再次仔细阅读有关 0/3/5 规则的链接,因为避免未定义的行为非常重要。
析构函数被多次调用的事实本身并不是问题。如果涉及副本,这是正常的。问题只是您的类复制操作已损坏。如果您不想在Get_In_Nodes
中创建副本并Get_Out_Nodes
,请改为返回 by-reference。
一般来说,您不应该像在现代C++中那样使用new
/delete
。相反,您可以使用std::make_unique
和std::unique_ptr
来拥有指针,如果您这样做,那么您显示的任何类都不需要自定义析构函数(至少不需要具有与隐式析构函数不同的有效行为的自定义析构函数(,并且您始终可以使用 0 规则,这意味着您也不需要实现任何复制操作。
如果这不是一个学习练习,你不应该首先写自己的清单。std::list
工作正常,几乎可以肯定比你想出的要好。
- 反向给定链表中的K节点
- 如果没有malloc,链表实现将失败
- 文本文件中的单词链表
- 努力将整数转换为链表。不知道我在这里做错了什么
- 链表,反向函数,数据结构
- 使用std::list创建循环链表
- 链表的泛型函数remove()与成员函数remove)
- 为什么不能修改对象中的值?另外,我如何改进此链表?
- 我们可以删除链表中静态内存中的节点吗
- C++,指针数组,指向双链表中的条目
- C++析构函数被意外调用双链表
- 如何获取链表中的所有"next"节点?构建调用字符串不起作用
- 类中的链表,并且应该通过该类中的函数调用
- 链表函数调用
- 在C++中调用链表函数
- 当我在调用append后立即实例化Node时,链表会损坏
- 每次在链表中调用insert_node()函数时,Head都被赋值为Null
- 如何在C++中调用递归链表遍历函数
- 调用在另一个对象内部定义的链表对象的函数
- 调用析构函数时不能删除链表