ptr:链接列表条目删除

我目前正在考虑在unique_ptrs的帮助下实施单个链表。 尽管由于析构函数的递归调用而导致堆栈溢出的问题(请参见使用unique_ptr链表的堆栈溢出),但我遇到了以下问题:假设我们有以下链接列表的实现

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,运营商=

通过调用reset(r.release()),然后从std :: forward(r.get_deleter())分配get_deleter(),将所有权从r转移到* this

仔细看看unique_ptr :: reset(http://en.cppreference.com/w/cpp/memory/unique_ptr/reset),它会读取

给定current_ptr,由* this管理的指针执行
按以下顺序进行操作:

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

  • 用参数current_ptr = ptr覆盖当前指针

  • 如果旧指针非空,则删除以前管理的对象(old_ptr!= nullptr)get_deleter()(old_ptr)

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

    非常感谢您的回复。


    你的担心似乎对我有效。 ptr-> next.get_deleter()不再存在,因为当ptr被重置时, ptr->next*ptr一起被销毁(尽管不再拥有任何东西)。 在大多数情况下,它可能无关紧要,因为删除器将是一个空基类,并且赋值将成为一个空操作,但仍然是技术上未定义的行为。

    我会用HEAD.next.reset(ptr->next.release()); (如果你不关心删除器),或者HEAD.next = std::unique_ptr<node>(std::move(ptr->next))如果你担心保留删除器。


    我认为你是对的。

    唯一防止HEAD.next->next unique_ptr被销毁的是HEAD.next node拥有它。 在尝试从HEAD.next->next unique_ptr获取删除器之前,分配给HEAD.next会中断。

    对我来说,用值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被销毁后使用它。


    我认为你错了,因为当你移动指针( std::move )时,你会产生一个右值并且使得ptr->next处于释放状态,所以它不是一个未定义的行为。

    我的意思是,在reset之前,你要释放指针: reset(r.release())

    接着:

    HEAD.next = std::move(HEAD.next->next);
    

    在评论后编辑(只需添加我正在测试的代码):

    struct custom_deleter {  // a deleter class with state
      custom_deleter() {}
      ~custom_deleter() {
        std::cout << "custom deleter deleted" << std::endl;
     }
      template <class T>
      void operator()(T* p) {
        std::cout << "custom deleter()" << std::endl;
        delete p;
      }
    };
    
    struct node {
      node (void) : val(0), next(nullptr) {}
      ~node () {
        std::cout << "~node " << val << std::endl;
      }
      int val;
      std::unique_ptr<node, custom_deleter> 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;
    
      std::cout << "And now we delete it" << std::endl;
      HEAD.next = std::move(HEAD.next->next);
    
      std::cout << "Looping" << std::endl;
      ptr = &HEAD;
      for (int i=0; i < 9; ++i) {
          std::cout << ptr->val << std::endl;
          ptr = ptr->next.get();
      }
      std::cout << "End." << std::endl;
    }
    

    和结果:

    custom deleter()
    ~node 1
    custom deleter deleted
    Looping
    0
    2
    3
    4
    5
    6
    7
    8
    9
    End.
    ~node 0
    custom deleter()
    ~node 2
    custom deleter()
    ~node 3
    custom deleter()
    ~node 4
    custom deleter()
    ~node 5
    custom deleter()
    ~node 6
    custom deleter()
    ~node 7
    custom deleter()
    ~node 8
    custom deleter()
    ~node 9
    custom deleter()
    ~node 10
    custom deleter deleted
    custom deleter deleted
    custom deleter deleted
    custom deleter deleted
    custom deleter deleted
    custom deleter deleted
    custom deleter deleted
    custom deleter deleted
    custom deleter deleted
    custom deleter deleted
    
    链接地址: http://www.djcxy.com/p/94193.html

    上一篇: ptr: linked list entry deletion

    下一篇: Reference .NET Core 1.0 library from WPF