在析构函数中删除单链表的正确方法是什么?
What is the proper way to delete a singly-linked-list in destructor?
你好,我对编写适当的析构函数有点含糊不清:
class SLLst
{
public:
SLLst() = default;
SLLst(const SLLst&);
SLLst& operator=(SLLst);
~SLLst();
void insert(int);
void remove(int);
private:
SLLst* next = nullptr;
int data = 0;
friend void swap(SLLst&, SLLst&);
friend std::ostream& print(std::ostream&, const SLLst&);
};
SLLst::SLLst(const SLLst& rhs) :
next(rhs.next ? new SLLst() : nullptr),
data(rhs.data)
{
cout << "cpy-ctor" << endl;
}
SLLst& SLLst::operator=(SLLst rhs)
{
cout << "operator=(SLLst)" << endl;
using std::swap;
swap(*this, rhs);
return *this;
}
void swap(SLLst& lhs, SLLst& rhs)
{
cout << "operator=(SLLst)" << endl;
using std::swap;
swap(lhs.next, rhs.next);
swap(lhs.data, rhs.data);
}
SLLst::~SLLst()
{
cout << "dtor" << endl;
delete next;// is this enough?
// or should I use this code?
//SLLst* cur = next;
//SLLst* n = nullptr;
//while (cur != NULL) {
// n = cur->next;
// cur->next = nullptr;
// delete cur;
// cur = n;
//}
}
void SLLst::insert(int x)
{
SLLst* tmp = new SLLst();
tmp->data = x;
if (!next)
{
next = tmp;
return;
}
tmp->next = next;
next = tmp;
}
std::ostream& print(std::ostream& out, const SLLst& lst)
{
auto tmp = lst.next;
while (tmp)
{
out << tmp->data << ", ";
tmp = tmp->next;
}
return out;
}
如您所见,如果我只是在析构函数中使用 delete next;
,那么我会在列表中调用尽可能多的节点,但为什么许多实现使用循环来释放节点,就像析构函数中的注释代码一样?
因为如果我只在 next
上调用 delete
那么析构函数将被递归调用因此我认为我不需要循环来释放析构函数中的节点?正确吗?
什么时候应该使用循环来释放析构函数中的节点?谢谢!
*如果我 运行 我的代码我会得到:
81, 77, 57, 23, 16, 7, 5,
done
dtor
dtor
dtor
dtor
dtor
dtor
dtor
dtor
- 如你所见,dtor 被调用了 8 次;这是否意味着它已正确释放所有节点?
As you can see if I just use delete next; in destructor then I get it called as many as nodes in the list
是的。
Because if I only call delete on next then the destructor will be called recursively thus I think I don't need a loop to free the nodes in destructor? is it correct?
是的。
When should I use a loop to free nodes in destructor?
当你需要的时候。
but why many implementations use a loop to free the nodes like the commented code in destructor?
因为很多实现都是"C-like",并且不使用析构函数。所以他们需要。
您正在充分利用 C++ 的对象管理功能 "do the loop for you"。耶!
(尽管,老实说,我还是会循环执行,因为你的方法可能会占用大量堆栈。)
现在更进一步,切换到std::list
(或std::forward_list
)。
立即开始,std::unique_ptr 会为您处理这件事,但如果您想自己做,那么它看起来像这样,请记住这是一个最小的例子。
RAII 是 C++ 中一个非常重要的原则,它基本上意味着,在你需要它的时候分配它,但在你用完它的时候销毁它。
因此,如果一个节点被指向,并且您删除了指向它的节点,那么该节点也应该销毁它指向的东西,因为它拥有它的所有权。
class List {
Node* first_node;
~List() {
delete first_node;
}
};
class Node {
~Node() {
delete next; // will then in turn destroy the one it points to untill one is nullptr, deleting nullptr is well defined in C++ nowadays
}
Node* next;
};
示例 std::unique_ptr
class List {
std::unique_ptr<Node> first_node;
// default dtor
};
class Node {
std::unique_ptr<Node> next;
// default dtor
};
你好,我对编写适当的析构函数有点含糊不清:
class SLLst
{
public:
SLLst() = default;
SLLst(const SLLst&);
SLLst& operator=(SLLst);
~SLLst();
void insert(int);
void remove(int);
private:
SLLst* next = nullptr;
int data = 0;
friend void swap(SLLst&, SLLst&);
friend std::ostream& print(std::ostream&, const SLLst&);
};
SLLst::SLLst(const SLLst& rhs) :
next(rhs.next ? new SLLst() : nullptr),
data(rhs.data)
{
cout << "cpy-ctor" << endl;
}
SLLst& SLLst::operator=(SLLst rhs)
{
cout << "operator=(SLLst)" << endl;
using std::swap;
swap(*this, rhs);
return *this;
}
void swap(SLLst& lhs, SLLst& rhs)
{
cout << "operator=(SLLst)" << endl;
using std::swap;
swap(lhs.next, rhs.next);
swap(lhs.data, rhs.data);
}
SLLst::~SLLst()
{
cout << "dtor" << endl;
delete next;// is this enough?
// or should I use this code?
//SLLst* cur = next;
//SLLst* n = nullptr;
//while (cur != NULL) {
// n = cur->next;
// cur->next = nullptr;
// delete cur;
// cur = n;
//}
}
void SLLst::insert(int x)
{
SLLst* tmp = new SLLst();
tmp->data = x;
if (!next)
{
next = tmp;
return;
}
tmp->next = next;
next = tmp;
}
std::ostream& print(std::ostream& out, const SLLst& lst)
{
auto tmp = lst.next;
while (tmp)
{
out << tmp->data << ", ";
tmp = tmp->next;
}
return out;
}
如您所见,如果我只是在析构函数中使用 delete next;
,那么我会在列表中调用尽可能多的节点,但为什么许多实现使用循环来释放节点,就像析构函数中的注释代码一样?
因为如果我只在
next
上调用delete
那么析构函数将被递归调用因此我认为我不需要循环来释放析构函数中的节点?正确吗?什么时候应该使用循环来释放析构函数中的节点?谢谢!
*如果我 运行 我的代码我会得到:
81, 77, 57, 23, 16, 7, 5,
done
dtor
dtor
dtor
dtor
dtor
dtor
dtor
dtor
- 如你所见,dtor 被调用了 8 次;这是否意味着它已正确释放所有节点?
As you can see if I just use delete next; in destructor then I get it called as many as nodes in the list
是的。
Because if I only call delete on next then the destructor will be called recursively thus I think I don't need a loop to free the nodes in destructor? is it correct?
是的。
When should I use a loop to free nodes in destructor?
当你需要的时候。
but why many implementations use a loop to free the nodes like the commented code in destructor?
因为很多实现都是"C-like",并且不使用析构函数。所以他们需要。
您正在充分利用 C++ 的对象管理功能 "do the loop for you"。耶!
(尽管,老实说,我还是会循环执行,因为你的方法可能会占用大量堆栈。)
现在更进一步,切换到std::list
(或std::forward_list
)。
立即开始,std::unique_ptr 会为您处理这件事,但如果您想自己做,那么它看起来像这样,请记住这是一个最小的例子。
RAII 是 C++ 中一个非常重要的原则,它基本上意味着,在你需要它的时候分配它,但在你用完它的时候销毁它。
因此,如果一个节点被指向,并且您删除了指向它的节点,那么该节点也应该销毁它指向的东西,因为它拥有它的所有权。
class List {
Node* first_node;
~List() {
delete first_node;
}
};
class Node {
~Node() {
delete next; // will then in turn destroy the one it points to untill one is nullptr, deleting nullptr is well defined in C++ nowadays
}
Node* next;
};
示例 std::unique_ptr
class List {
std::unique_ptr<Node> first_node;
// default dtor
};
class Node {
std::unique_ptr<Node> next;
// default dtor
};