什么时候对象“超出范围”?

新手上路,请多包涵

在 C++ 中,何时将对象定义为“超出范围”?

更具体地说,如果我有一个单链表,什么会将单个列表节点对象定义为“超出范围”?或者,如果一个对象存在并且被一个变量 ptr 引用,那么当引用被删除或指向不同的对象时,说该对象被定义为“超出范围”是否正确?

更新:假设一个对象是一个具有实现析构函数的类。对象退出作用域时会调用析构函数吗?

 if (myCondition) {
    Node* list_1 = new Node (3);
    Node* list_2 = new Node (4);
    Node* list_3 = new Node (5);

    list_1->next = list_2;
    list_2->next = list_3;
    list_3->next = null;
}

换句话说,被 list_1 指向的节点是否会在此行之后调用其析构函数:

 Node* list_1 = new Node (3);

?

原文由 Pat Murray 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 766
2 个回答

首先,请记住 C++ 中的对象既可以 在堆栈 上创建,也可以 在堆上创建。

堆栈帧(或范围)由语句定义。这可以像函数一样大,也可以像流控制块一样小( while / if / for 等)。包含任意代码块的任意 {} 对也构成堆栈帧。一旦程序退出该框架,在框架内定义的任何局部变量都将超出范围。当堆栈变量超出范围时,将调用其析构函数。

所以这是一个堆栈帧(函数的执行)和在其中声明的局部变量的经典示例,一旦堆栈帧退出,它将超出范围 - 一旦函数完成:

 void bigSideEffectGuy () {
    BigHeavyObject b (200);
    b.doSomeBigHeavyStuff();
}
bigSideEffectGuy();
// a BigHeavyObject called b was created during the call,
// and it went out of scope after the call finished.
// The destructor ~BigHeavyObject() was called when that happened.

这是一个示例,我们看到堆栈帧只是 if 语句的主体:

 if (myCondition) {
    Circle c (20);
    c.draw();
}
// c is now out of scope
// The destructor ~Circle() has been called

退出框架后堆栈创建的对象“保留在范围内”的唯一方法是它是否是函数的返回值。但这并不是真正的“保留在范围内”,因为正在复制对象。所以原件超出了范围,但制作了一份副本。例子:

 Circle myFunc () {
    Circle c (20);
    return c;
}
// The original c went out of scope.
// But, the object was copied back to another
// scope (the previous stack frame) as a return value.
// No destructor was called.

现在,也可以在堆上声明对象。为了便于讨论,将堆视为无定形的内存块。与堆栈不同,堆栈会在您进入和退出堆栈帧时自动分配和取消分配必要的内存,您必须手动保留和释放堆内存。

在堆上声明的对象确实在堆栈帧之间“存活”。可以说在堆上声明的对象永远不会超出范围,但这实际上是因为该对象从未真正与任何范围相关联。这样的对象必须通过 new 关键字创建,并且必须由指针引用。

完成后释放堆对象是您的责任。您使用 delete 关键字释放堆对象。在释放对象之前,不会调用堆对象上的析构函数。

指向堆对象的指针本身通常是与作用域相关的局部变量。使用完堆对象后,就允许引用它的指针超出范围。如果您没有显式释放指针指向的对象,那么堆内存块将永远不会被释放,直到进程退出(这称为内存泄漏)。

可以这样想:在堆栈上创建的对象就像用胶带粘在房间里的椅子上的气球。当你离开房间时,气球会自动弹出。在堆上创建的对象就像丝带上的气球,系在房间的椅子上。丝带是指针。当你离开房间时,丝带会自动消失,但气球只是漂浮在天花板上并占据空间。正确的做法是用别针戳破气球,然后离开房间,丝带就会消失。但是,绳子上的气球的好处是您还可以解开丝带,将其握在手中,然后离开房间并随身携带气球。

因此,转到您的链表示例:通常,此类列表的节点在堆上声明,每个节点都持有指向下一个节点的指针。所有这些都在堆上,永远不会超出范围。唯一可能超出范围的是指向列表根的指针 - 您首先用来引用列表的指针。这可能超出范围。

这是在堆上创建东西的示例,并且根指针超出范围:

 if (myCondition) {
    Node* list_1 = new Node (3);
    Node* list_2 = new Node (4);
    Node* list_3 = new Node (5);

    list_1->next = list_2;
    list_2->next = list_3;
    list_3->next = null;
}
// The list still exists
// However list_1 just went out of scope
// So the list is "marooned" as a memory leak

原文由 sparc_spread 发布,翻译遵循 CC BY-SA 3.0 许可协议

当执行离开该部分代码时,在函数内部(或在函数内部某些花括号括起来的构造内部)声明的对象将超出范围。

 void some_func() {
  std::string x("Hello!");
  // x is in scope here
}
// But as soon as some_func returns, x is out of scope

这仅适用于在堆栈上声明的内容,因此它与单链表几乎没有关系,因为列表节点通常会在堆上实例化为 new

在这个例子中,由 new 返回的 指针 将在函数退出时超出范围,但节点本身不会发生任何事情:

 void make_a_node() {
  Node* p = new Node;
} // Oh noes a memory leak!

原文由 DSimon 发布,翻译遵循 CC BY-SA 3.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题