为什么在函数内用 new 关键字声明实例指针会导致问题和未定义的行为?

Why does declaring an instance pointer with new keyword inside a function causes problems and undefined behaviour?

我正在学习 C++,在解决 HackerRank 平台上的挑战时遇到了一些有趣的事情。代码来自链表问题。目的是在Solutionclass中用display方法显示链表。我注意到我必须在初始化 class 实例时使用 new 关键字才能工作,否则代码不会检测到列表的末尾。请看下面两段代码及其各自的输出。

为什么在第二个代码示例中没有使用 new 关键字时,无法正确检测到链表的末尾?我在网上看到不鼓励使用 new 因为之后需要明确删除指针以避免内存泄漏,所以我很困惑。

两个代码使用相同的以下输入:

输入

4
2
3
4
1

代码 1

#include <iostream>
#include <cstddef>
using namespace std;    
class Node
{
    public:
        int data;
        Node *next;
        Node(int d){
            data=d;
            next=NULL;
        }
};
class Solution{
    public:

      Node* insert(Node *head,int data)
      {
          //Complete this method
        Node* new_node = new Node(data); //Check this line
        if (head) {
          Node *current = head;
          while(current->next) {
            current = current->next;
          }
            current->next = new_node;
        }
        else
        {
          head = new_node;

        }
        //   Node** pp = &head;
        //   while (*pp) {
        //       pp = &((*pp)->next);
        //   }
        //   *pp = new Node(data);
        return head;
   
      }

      void display(Node *head)
      {
          Node *start=head;
          while(start)
          {
              cout<<start->data<<" ";
              start=start->next;
          }
      }
};
int main()
{
    Node* head=NULL;
    Solution mylist;
    int T,data;
    cin>>T;
    while(T-->0){
        cin>>data;
        head=mylist.insert(head,data);
    }   
    mylist.display(head);
        
}

输出 1(正确)

2 3 4 1 

代码 2

#include <iostream>
#include <cstddef>
using namespace std;    
class Node
{
    public:
        int data;
        Node *next;
        Node(int d){
            data=d;
            next=NULL;
        }
};
class Solution{
    public:

      Node* insert(Node *head,int data)
      {
          //Complete this method
        Node new_node(data);
        if (head) {
          Node *current = head;
          while(current->next) {
            current = current->next;
          }
            current->next = &new_node;
        }
        else
        {
          head = &new_node;

        }
        //   Node** pp = &head;
        //   while (*pp) {
        //       pp = &((*pp)->next);
        //   }
        //   *pp = new Node(data);
        return head;
   
      }

      void display(Node *head)
      {
          Node *start=head;
          while(start)
          {
              cout<<start->data<<" ";
              start=start->next;
          }
      }
};
int main()
{
    Node* head=NULL;
    Solution mylist;
    int T,data;
    cin>>T;
    while(T-->0){
        cin>>data;
        head=mylist.insert(head,data);
    }   
    mylist.display(head);
        
}

输出 2(不正确)

1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1....

虽然使用 new 确实需要显式释放,但这也意味着如果您不使用 new,那么对象的生命周期已经以某种方式隐式管理。

这是你的第二种情况:

Node new_node(data);
head = &new_node;

这里你实际上是获取局部变量的地址并将其分配给全局变量。但是当函数 returns 和您存储的地址不再有效时,局部变量本身将不复存在。

当您使用 new 时,对象的内存分配在一个独立的堆中,并且只要您不 delete 它就会一直存在。

PS。在 C++ 中,您可以(并且应该)使用 nullptr 而不是 NULL

如前所述,您的数据结构指向一个本地对象,该对象在函数返回时已经超出范围。访问 non-existent 对象会导致未定义的行为。

您似乎试图避免使用 new。避免显式使用 new 的一种方法是使用智能指针来表示您的 Node.

基于 unique_ptr 的解决方案可能类似于:

struct Node;
using NodePtr = std::unique_ptr<Node>;

因此,您可以使用 make_unique 而不是调用 new

class Node {
    friend class Solution;

    int data_;
    NodePtr next_;

    static NodePtr make (int d) {
        return std::make_unique<Node>(d);
    }

public:
    Node (int d) : data_(d) {}

};

为避免搜索最后一个元素,您可以始终在列表中维护对最后一个元素的引用。我在 std::reference_wrapper 周围实现了一个名为 NodeRef 的垫片,使其更像一个指针。

class Solution {
    NodePtr head_;
    NodeRef tail_;

public:
    Solution () : tail_(head_) {}

    void insert (int d) {
        *tail_ = Node::make(d);
        tail_ = tail_->next_;        
    }

    void display () {
        NodeRef p = head_;
        while (p) {
            std::cout << p->data_ << ' ';
            p = p->next_;
        }
    }
};

Try it online!