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

时间:2012-04-09 22:59:58

标签: c++ scope destructor

在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);

5 个答案:

答案 0 :(得分:45)

首先,请记住,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

答案 1 :(得分:5)

{ //scope is defined by the curly braces
    std::vector<int> vec;
}
// vec is out of scope here!
vec.push_back(15);

答案 2 :(得分:2)

“超出范围”是一个转喻:就像在,使用一个概念的名称或术语来谈论密切相关但不同的东西。

在C ++中,范围是程序文本的静态区域,因此字面意义上的“超出范围”意味着在文本区域之外。例如,{ int x; } int y;y的声明超出x可见的范围。

转喻“超出范围”用于表达与某个范围相关联的环境的动态激活/实例化终止的想法。因此,该范围中定义的变量将消失(因此“超出范围”)。

实际上“超出范围”的是指令指针,可以这么说;该计划的评估现在正在一个对该目标没有可见性的范围内进行。但并非范围内的所有内容都消失了!下次输入范围时,静态变量仍然存在。

“超出范围”不是很准确,但很短,每个人都明白这意味着什么。

答案 3 :(得分:2)

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

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!

答案 4 :(得分:1)

当它离开声明范围时:)

如果没有看到实施,你现在的问题是无法回答的。它归结为您声明此节点的位置。

void Foo()
{
    int i = 10;

    {
        int j = 20;
    } // j is out of scope

} // i is out of scope