两个程序的区别?

Difference between two program?

我使用列表节点创建了一个堆栈 class。 但是一个工作正常,另一个总是崩溃。 两个程序有什么区别? 我认为析构函数会破坏

#include<iostream>
using namespace std;

class Stack
{
public:
    int pop() {
        data = next->data;
        auto tmp = next;
        next = next->next;
        delete tmp;
        return data;
    }
    void push(int n) {
        Stack* p = new Stack();
        p->data = n;
        p->next = next;
        next = p;
    }
    virtual ~Stack() {
        free();
    }
    void free() {
        while(next) pop();
    }

protected:
    int data;
    Stack* next = nullptr;
};
int main()
{
    Stack s;
    s.push(1);
    s.push(2);
    //s.free();
}

以上程序总是崩溃..

#include<iostream>
using namespace std;

class Stack
{
public:
    int pop() {
        data = next->data;
        auto tmp = next;
        next = next->next;
        delete tmp;
        return data;
    }
    void push(int n) {
        Stack* p = new Stack();
        p->data = n;
        p->next = next;
        next = p;
    }
    virtual ~Stack() {
    //  free();
    }
    void free() {
        while(next) pop();
    }

protected:
    int data;
    Stack* next = nullptr;
};
int main()
{
    Stack s;
    s.push(1);
    s.push(2);
    s.free();
}

这个程序工作正常。

两者有什么区别?

当您在 pop 中删除 tmp 时,您会调用 Stack 的析构函数,在您的第一个代码中它会加倍 free:

这是推送 1 和 2 后堆栈的状态(p1p2 是在 push 中创建的新 Stack 对象):

   |    data       |     next
-------------------------------
s  | uninitialized |      p1
p1 |      1        |      p2
p2 |      2        |    nullptr

现在,如果您像在第一个代码中那样销毁 s(忽略 data 发生的情况):

  • s.free被执行。
  • s.nextp1,所以执行s.pop
  • tmp变成s.next,也就是p1.
  • s.next变成s.next->next,也就是p1->next,也就是p2
  • tmp也就是p1被删除了,所以调用了p1的析构函数。此时,我们处于以下状态:

       |    data       |     next
    -------------------------------
    s  | uninitialized |      p2
    p1 |      1        |      p2
    p2 |      2        |    nullptr
    
  • p1的析构函数调用free,后者调用pop(因为p1->nextp2)。

  • p2通过tmp删除。
  • 返回 s 的析构函数,状态如下:

       |    data       |     next
    -------------------------------
    s  | uninitialized |      p2 (dangling)
    p2 |      2        |    nullptr
    

在您的第二个代码中,p1 的析构函数 不会 调用 free,并且不会使 p2 成为悬挂指针,所以代码 "works fine".

你应该这样修改pop

int pop() {
    data = next->data;
    auto tmp = next;
    next = next->next;
    /***********************/
    next->next = nullptr;
    /***********************/
    delete tmp;
    return data;
}

顺便说一句,如果您使用的是 C++11,则应使用托管指针重新设计代码。类似的东西:

#include<iostream>
#include<memory>
using namespace std;

class Stack
{
public:
    int pop() {
        data = next->data;
        next = move(next->next);
        return data;
    }
    void push(int n) {
        auto p = make_unique<Stack>();
        p->data = n;
        p->next = move(next);
        next = move(p);
    }
    void free() {
        while(next) pop();
    }

protected:
    int data;
    unique_ptr<Stack> next;
};
int main()
{
    Stack s;
    s.push(1);
    s.push(2);
}

由于 make_unique<Stack>() 是 C++14,您可以将其替换为 unique_ptr<Stack>(new Stack)

首先,您的 pop 已损坏 - delete tmp; 递归删除以 tmp 开头的整个子堆栈,而不仅仅是顶部节点。
tmp 的析构函数也会 free();,依此类推。)
这会导致双重删除。

您的第二个版本会泄漏内存,除非您记得在完成堆栈后调用 free()

您可以保持析构函数简单并解决这两个问题:

virtual ~Stack() {
    delete next;
}

free可以是

void free()
{
    delete next;
    next = nullptr;
}

(如果您要丢弃结果,则无需进行大量指针交换和元素复制。)