我目前正在考虑在unique_ptrs的帮助下实现单个链表。尽管由于析构函数的递归调用可能导致堆栈溢出(请参阅Stack overflow with unique_ptr linked list),但我遇到了以下问题: 假设我们有链接列表的以下实现
struct node {
node (void) : val(0), next(nullptr) {}
int val;
std::unique_ptr<node> next;
};
我们已根据
初始化了我们的列表int main (int argc, char* argv[]) {
node HEAD;
HEAD.val = 0;
auto ptr = &HEAD;
for (int i = 0; i < 10; ++i) {
ptr->val = i;
ptr->next.reset(new node);
ptr = ptr->next.get();
}
ptr->val = 10;
...
现在,我想删除值为1的节点:
ptr = &HEAD;
ptr = ptr->next.get();
HEAD.next = std::move(ptr->next);
乍一看,这似乎是明智的。不过,我不确定它是否会导致未定义的行为:
根据http://en.cppreference.com/w/cpp/memory/unique_ptr/operator%3D, 运算符=
将所有权从r转移到*,就好像通过调用reset(r.release()) 然后从std :: forward分配get_deleter() (r.get_deleter())
仔细查看unique_ptr :: reset(http://en.cppreference.com/w/cpp/memory/unique_ptr/reset),它会读取
给定current_ptr,由* this管理的指针执行
以下操作,按此顺序:
保存当前指针的副本old_ptr = current_ptr
- 覆盖当前指针
使用参数current_ptr = ptr
- 醇>
如果旧指针非空,则删除以前管理的对象 if(old_ptr!= nullptr)get_deleter()(old_ptr)
这意味着在这种情况下,r.get_deleter()的调用会引用一个已被销毁的对象,或者我在这里弄错了?
非常感谢您的回复。
答案 0 :(得分:3)
您的担忧似乎对我有用。 ptr-&gt; next.get_deleter()不再存在,因为重置ptr->next
时*ptr
被销毁(虽然不再拥有任何东西)和ptr
。在大多数情况下,它可能不重要,因为删除器将是一个空的基类,并且赋值将是一个无操作,但仍然是技术上未定义的行为。
我明确地使用HEAD.next.reset(ptr->next.release());
(如果您不关心删除者),或者HEAD.next = std::unique_ptr<node>(std::move(ptr->next))
如果您担心保留删除者。
答案 1 :(得分:1)
我认为你是对的。
阻止HEAD.next->next
unique_ptr
被破坏的唯一因素是HEAD.next
node
拥有它。分配给HEAD.next
的行为会在尝试从HEAD.next->next
unique_ptr
获取删除之前打断。
对我来说,删除值为1的节点的直观方法是将尾部从头部分离,然后从值为2的节点重新附加尾部:
auto tail = std::move(HEAD.next);
HEAD.next = std::move(tail.next);
你想要做的就像切割和重新连接一条蛇,而只用一只手握住蛇,如果你不小心蛇可能会蠕动。
编辑:我尝试使用这样的有状态删除器:
#include <memory>
#include <iostream>
struct node;
class NodeDeleter {
int deleterNumber;
public:
NodeDeleter() : deleterNumber(-1) {}
NodeDeleter(int deleterNumber) : deleterNumber(deleterNumber) {}
void operator()(node* n);
};
struct node {
node() : val(0), next(nullptr) {}
int val;
std::unique_ptr<node, NodeDeleter> next;
};
void NodeDeleter::operator()(node* n) {
std::cout << "Deleting node with value " << n->val << " using deleter " << deleterNumber << "\n";
delete n;
}
std::unique_ptr<node, NodeDeleter> make_node(int deleterNumber) {
return std::unique_ptr<node, NodeDeleter>(new node(), NodeDeleter(deleterNumber));
}
int main() {
node HEAD;
HEAD.val = 0;
auto ptr = &HEAD;
for (int i = 0; i < 10; ++i) {
ptr->val = i;
ptr->next = make_node(i);
ptr = ptr->next.get();
}
ptr->val = 10;
HEAD.next = std::move(HEAD.next->next);
}
有趣的是,在GCC中,它似乎表现得很好,但在Visual Studio 2015中,它显然是在调用未定义的行为。输出是:
Deleting node with value 1 using deleter 0
Deleting node with value 2 using deleter -572662307
Deleting node with value 3 using deleter 2
Deleting node with value 4 using deleter 3
...
看起来它试图在删除后使用删除器1。