unique_ptr:链表项删除

时间:2016-12-02 16:16:11

标签: c++ linked-list undefined-behavior unique-ptr

我目前正在考虑在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管理的指针执行
  以下操作,按此顺序:

     
      
  1. 保存当前指针的副本old_ptr = current_ptr

  2.   
  3. 使用参数current_ptr = ptr

  4. 覆盖当前指针   
  5. 如果旧指针非空,则删除以前管理的对象   if(old_ptr!= nullptr)get_deleter()(old_ptr)

  6.   

这意味着在这种情况下,r.get_deleter()的调用会引用一个已被销毁的对象,或者我在这里弄错了?

非常感谢您的回复。

2 个答案:

答案 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。