【问题标题】:Singly linked list with unique_ptr带有 unique_ptr 的单向链表
【发布时间】:2022-11-23 13:11:38
【问题描述】:

我正在尝试使用智能指针 (std::unique_ptr) 创建一个单链表。这是一个带有原始指针的单链表的例子。

struct Node {
  int data;
  Node *next = nullptr;
  Node(int data) : data{data}, next{nullptr} {}
  ~Node() { std::cout << "Destroy node with data: " << data << '\n'; }
};

void print_list(Node *head) {
  while (head != nullptr) {
    cout << head->data << " --> ";
    head = head->next;
  }
  cout << "nullptr" << std::endl;
}

void insert(Node *&head, int data) {
  Node *new_node = new Node{data};
  new_node->next = head;
  head = new_node;
}

int main(int argc, char *argv[]) {
  Node *head = nullptr;
  for (int i = 0; i < 5; ++i) {
    insert(head, i);
  }
  print_list(head);
  return 0;
}

输出是:

4 --> 3 --> 2 --> 1 --> 0 --> nullptr

显然上面的代码中存在内存泄漏(未调用析构函数)。现在我想用智能指针来实现同样的事情:

struct Node {
  int data = 0;
  std::unique_ptr<Node> next;
  Node(int data) : data{data}, next{nullptr} {}
  ~Node() { std::cout << "Destroy node with data: " << data << '\n'; }
};

void print_list(std::unique_ptr<Node> head) {
  while (head != nullptr) {
    std::cout << head->data << " --> ";
    head = std::move(head->next);
  }
  std::cout << "nullptr" << std::endl;
}

void insert(std::unique_ptr<Node> &&head, int data) {
  std::unique_ptr<Node> new_node{std::make_unique<Node>(data)};
  new_node->next = std::move(head);
  head = std::move(new_node);
}

// g++ -std=c++17 -Wall 2_1.cpp && ./a.out
int main(int argc, char *argv[]) {
  std::unique_ptr<Node> head{nullptr};
  for (int i = 0; i < 5; ++i) {
    insert(std::move(head), i);
  }
  print_list(std::move(head));
  return 0;
}

输出是:

4 --> Destroy node with data: 4
3 --> Destroy node with data: 3
2 --> Destroy node with data: 2
1 --> Destroy node with data: 1
0 --> Destroy node with data: 0
nullptr

我们可以观察到 new_node 的生命周期在 insert() 返回时结束。我想知道是否可以使用智能指针来实现单链表并保留上面的函数接口。

【问题讨论】:

    标签: c++ linked-list smart-pointers unique-ptr


    【解决方案1】:

    首先,您的print_list 实现有问题(对于两个版本)。使用 print_list,您将 head 移动到最后一个元素。这意味着一旦您打印了列表,您就无法访问该列表,这可能是不希望的。相反,在你的print_list 中,你应该首先创建一个指向head 的临时指针,然后只迭代临时指针。


    现在到你的unique_ptr版本,你不必传递一个unique_ptr 作为右值引用,你也可以将它作为左值引用传递。相反,您的函数签名可能如下所示:

    void print_list(const std::unique_ptr<Node>& head);
    void insert(std::unique_ptr<Node> &head, int data);
    

    这使您无需在main 中使用std::move 即可调用它们。


    现在开始定义。对于您的插入,您所拥有的是首先使用给定值创建一个新的Node,然后将旧的head分配给新节点的next,并将新节点作为新的head

    void insert(std::unique_ptr<Node> &head, int data)
    {
        // Use `auto` to avoid typing `....<Node>` twice
        auto new_node = std::make_unique<Node>(data);
        new_node->next = std::move(head);
        head = std::move(new_node);
    }
    

    或者,您也可以在Node 的构造函数中再添加一个参数:

    Node(int data, std::unique_ptr<Node>&& next = nullptr) 
    : data{data}, next{std::move(next)}
    {}
    

    现在你可以像这样简单地创建new_node

    void insert(std::unique_ptr<Node> &head, int data)
    {
        // No need to assign `Node::next` separately
        auto new_node = std::make_unique<Node>(data, std::move(head));
        head = std::move(new_node);
    }
    

    或者甚至跳过创建新节点:

    void insert(std::unique_ptr<Node> &head, int data)
    {
        head = std::make_unique<Node>(data, std::move(head));
    }
    

    对于print_list,我们应该首先创建一个指向head的底层对象的临时指针,然后通过将临时指针分配给它的下一个对象的底层对象来迭代列表:

    void print_list(const std::unique_ptr<Node>& head)
    {
        // Create a pointing to the underlying object
        // You can use `.get()` to get the underlying pointer
        auto current = head.get();
    
        // No need to explicit compare pointer types to `nullptr`
        while (current) {
            std::cout << current->data << " --> ";
            // Make `current` point to the next underlying object
            current = current->next.get();
        }
        std::cout << "nullptr" << std::endl;
    }
    

    Demo

    【讨论】:

      猜你喜欢
      • 2013-03-20
      • 2013-03-01
      • 2012-11-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多