如何判断对象是在栈内存还是堆内存

Def*_*ult 1 c++ list heap-memory stack-memory

我最近收到了一份数据结构课程的大学作业,要求我用 C++ 创建一个双向链表

在处理双向链表时,我需要实现各种功能,但特别引起我注意的一种方法是“clear()”。该方法负责清除双向链表内的所有元素:

void clear(Node* head_ptr)
{
    Node* previous_ptr = nullptr;

    while(head_ptr != nullptr)
    {
        previous_ptr = head_ptr; // Store previous node.

        head_ptr = head_ptr->next;

        delete previous_ptr;
    }
};
Run Code Online (Sandbox Code Playgroud)

该方法非常简单;它只是迭代所有元素并为每个元素释放内存Node。然后,我在析构函数中调用此方法,如下所示:

~List()
{
    clear_list(m_head_ptr);
};
Run Code Online (Sandbox Code Playgroud)

然后我开始思考。如果我的节点元素位于堆上,这种释放内存的方法就很好,如下所示:

int main()
{
    List list;

    Node* node_1 = new Node(3, nullptr);    // The tail node.
    Node* node_2 = new Node(1, node_1);
    Node* node_3 = new Node(5, node_2);
    Node* node_4 = new Node(7, node_3);     // The head node. 
    
    list.add(node_1);
    list.add(node_2);
    list.add(node_3);
    list.add(node_4);

    // Then do some stuff with the list...

}   // The list goes out of scope and the destructor is called...
Run Code Online (Sandbox Code Playgroud)

但是,一旦我在堆栈上创建 s并将Node指针传递给堆栈对象,这种情况就会中断,如下所示:

int main()
{
    List list;

    Node* node_1(3, nullptr);    // The tail node.
    Node* node_2(1, node_1);
    Node* node_3(5, node_2);
    Node* node_4(7, node_3);     // The head node. 
    
    list.add(&node_1);
    list.add(&node_2);
    list.add(&node_3);
    list.add(&node_4);

    // Then do some stuff with the list...

}   // The list goes out of scope and the destructor is called and the program crashes because it attempts to free stack objects...
Run Code Online (Sandbox Code Playgroud)

原因是因为我试图释放堆栈对象,这不是一个好主意。当然,我们通常不会使用基于堆栈Node的,因为我们通常希望Node数据在它们创建的范围之外持续存在。尽管如此,这还是引出了我的问题:

我该如何应对?有没有办法检查内存中的某些内容是否Node在我的函数中的堆或堆栈上,然后相应地释放它?或者,有更好的方法来解决这个问题吗?

Jer*_*fin 8

最简单、最有效的解决方案通常是让列表自行管理节点的分配。用户甚至不需要知道类型的node存在,更不用说分配它们等等。

因此,对于用户来说,您在问题中显示的内容相当于以下内容:

List list;
list.add_head(1);
list.add_head(3);
list.add_head(5);
list.add_head(7);
Run Code Online (Sandbox Code Playgroud)

您通常还需要将add_tail项目添加到列表末尾。每个add(头或尾)通常应该返回一个抽象iterator类型(这可能是指向节点的指针的包装器),因此您可以执行以下操作:

auto pos = list.add_head(7);
list.add_tail(5);
list.add_after(pos, 3);
Run Code Online (Sandbox Code Playgroud)

...这会在开头添加 7,在结尾添加 5,并37.

这样,列表本身就会分配所有节点,并知道如何处理它们。您可以更进一步,将分配和处置委托给一个Allocator类。这当然很有用,但可能有点超出了基本练习的意义(在实际使用中,您可能想使用标准库中的容器——虽然标准库确实提供了单向和双向——链表,它们很少有用)。

  • @Default 每个文件一个类是一种很好的、​​有序的做事方式,但有时这是错误的方式。将标头视为对接口的描述,在接口中公开的后端详细信息越少越好,因为它会降低您将来更改接口的能力。节点类(与使用它的链表类紧密耦合的东西)也可能位于同一个标头中。由于对用户隐藏节点类有显着的好处,因此它应该位于链表类内部并保持在视线之外。 (2认同)