【问题标题】:When is an object "out of scope"?什么时候对象“超出范围”?
【发布时间】:2012-04-22 06:46:16
【问题描述】:

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

?

【问题讨论】:

  • 您的更新问题是一个非常好的问题,答案是否定的。当指针超出范围时,它所指向的对象的析构函数不会自动调用。您必须自己调用它,当您在节点上调用delete 以释放内存时,就会发生这种情况。当您拥有指向具有有意义的析构函数实现的对象的数组或指针列表时,请记住这一点。幸运的是,既然你必须自己 free 这些,你将触发析构函数。
  • 顺便说一句,如果你真的需要一个链表来做某事,你最好使用 std::list 而不是你自己的自定义链表。
  • 我应该说delete 而不是free。忽略free,它是 C 遗留的。仅使用 newdelete 进行堆分配/删除。

标签: c++ scope destructor


【解决方案1】:

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

【讨论】:

  • 感谢您的意见。所以我的最后一个问题是(如果 list_1 有析构函数)是否会尽快调用析构函数... list_1->next = list_2; ...被执行了吗?
  • 这与您的其他后续行动略有不同,因此我会同时回答。答案是否定的,因为您在这里所做的只是将next 字段设置为指向已分配的节点——没有任何事情超出范围。但即使某些东西确实超出了范围,也不会调用析构函数,因为那将是一个指针(请参阅您的主要问题的后续评论。)
  • 请注意,在堆栈上创建的对象在超出范围时调用其析构函数。
  • A stack frame (or scope) is defined by a {} pair, 不正确。它由一个语句定义,{} 定义了一个复合语句,它也是一个范围。见:ideone.com/oIqwdW
  • 那个气球类比是迄今为止我读过的最糟糕的例子。这个答案中的其他所有内容都写得很好
【解决方案2】:
{ //scope is defined by the curly braces
    std::vector<int> vec;
}
// vec is out of scope here!
vec.push_back(15);

【讨论】:

    【解决方案3】:

    当它离开它声明的范围时:)

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

    void Foo()
    {
        int i = 10;
    
        {
            int j = 20;
        } // j is out of scope
    
    } // i is out of scope
    

    【讨论】:

      【解决方案4】:

      “超出范围”是一种转喻:例如,使用一个概念的名称或术语来谈论密切相关但不同的事物。

      在 C++ 中,作用域是程序文本的静态区域,因此“超出作用域”的字面意思是物理上位于文本区域之外。比如{ int x; } int y;y的声明超出了x可见的范围。

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

      实际上“超出范围”的是指令指针,可以这么说;该计划的评估现在是在一个不可见的范围内进行的。但并不是范围内的所有东西都消失了!下次进入作用域时静态变量仍然存在。

      “超出范围”不是很准确,但是简短,大家都明白是什么意思。

      【讨论】:

        【解决方案5】:

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

        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!
        

        【讨论】:

        • 如果 Node 类实现了析构函数,p 引用的 Node 是否会在 p 退出作用域时调用析构函数?
        • 不。但是,在我的第一个示例中,当 x 离开作用域时,将调用 std::string 析构函数 。在第二个示例中,离开作用域的不是 Node 而是 Node*,并且指针本身没有析构函数。
        • 但是如果你使用std::unique_ptr&lt;Node&gt; p = new Node;而不是原始指针,则当p超出范围时,节点对象的析构函数将在退出时被调用..
        猜你喜欢
        • 2016-03-17
        • 1970-01-01
        • 2011-08-10
        • 1970-01-01
        • 1970-01-01
        • 2022-08-10
        • 1970-01-01
        • 2021-01-25
        • 1970-01-01
        相关资源
        最近更新 更多