37

In C++, when is an object defined as "out of scope"?

More specifically, if I had a singly linked list, what would define a single list node object as "out of scope"? Or if an object exists and is being referenced by a variable ptr, is it correct to say that the object is defined as "out of scope" the moment the reference is deleted or points to a different object?

UPDATE: Assuming an object is a class that has an implemented destructor. Will the destructor be called the moment the object exits the scope?

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;
}

In other words, would the Node being pointed to by list_1 call its destructor after this line:

Node* list_1 = new Node (3);

?

AAEM
  • 1,837
  • 2
  • 18
  • 26
Pat Murray
  • 4,125
  • 7
  • 28
  • 33
  • 2
    Your update questions is a very good one, and the answer is, no. When a pointer goes out of scope, the destructor of the object to which it is pointing is **not** automatically called. You have to call it yourself, and this happens when you call `delete` on the node to free up the memory. This is a good thing to keep in mind when you have an array or list of pointers to objects with meaningful destructor implementations. Luckily, since you have to `free` these yourself anyway, you'll be triggering the destructors. – sparc_spread Apr 10 '12 at 00:12
  • By the way, if you actually need a linked list for something, you're much better off using std::list than your own custom linked list. – DSimon Apr 10 '12 at 02:10
  • 2
    I should have said `delete` instead of `free`. Ignore `free`, it is C legacy. Use only `new` and `delete` for heap allocation/deletion. – sparc_spread Apr 10 '12 at 19:36

5 Answers5

69

First, remember that objects in C++ can be created either on the stack or on on the heap.

A stack frame (or scope) is defined by a statement. That can be as big as a function or as small as a flow control block (while/if/for etc.). An arbitrary {} pair enclosing an arbitrary block of code also constitutes a stack frame. Any local variable defined within a frame will go out of scope once the program exits that frame. When a stack variable goes out of scope, its destructor is called.

So here is a classic example of a stack frame (an execution of a function) and a local variable declared within it, which will go out of scope once the stack frame exits - once the function finishes:

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.

Here is an example where we see a stack frame being just the body of an if statement:

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

The only way for a stack-created object to "remain in scope" after the frame is exited is if it is the return value of a function. But that is not really "remaining in scope" because the object is being copied. So the original goes out of scope, but a copy is made. Example:

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.

Now, an object can also be declared on the heap. For the sake of this discussion, think of the heap as an amorphous blob of memory. Unlike the stack, which automatically allocates and de-allocates the necessary memory as you enter and exit stack frames, you must manually reserve and free heap memory.

An object declared on the heap does, after a fashion, "survive" between stack frames. One could say that an object declared on the heap never goes out of scope, but that's really because the object is never really associated with any scope. Such an object must be created via the new keyword, and must be referred to by a pointer.

It is your responsibility to free the heap object once you are done with it. You free heap objects with the delete keyword. The destructor on a heap object is not called until you free the object.

The pointers that refer to heap objects are themselves usually local variables associated with scopes. Once you are done using the heap object, you allow the pointer(s) referring to it to go out of scope. If you haven't explicitly freed the object the pointer is pointing to, then the block of heap memory will never be freed until the process exits (this is called a memory leak).

Think of it all this way: an object created on the stack is like a balloon taped to a chair in a room. When you exit the room, the balloon automatically pops. An object created on the heap is like a balloon on a ribbon, tied to a chair in a room. The ribbon is the pointer. When you exit the room, the ribbon automatically vanishes, but the balloon just floats to the ceiling and takes up space. The proper procedure is to pop the balloon with a pin, and then exit the room, whereupon the ribbon will disappear. But, the good thing about the balloon on the string is you can also untie the ribbon, hold it in your hand, and exit the room and take the balloon with you.

So to go to your linked list example: typically, nodes of such a list are declared on the heap, with each node holding a pointer to the next node. All of this is sitting on the heap and never goes out of scope. The only thing that could go out of scope is the pointer that points to the root of the list - the pointer you use to reference into the list in the first place. That can go out of scope.

Here's an example of creating stuff on the heap, and the root pointer going out of scope:

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
  • 10,643
  • 11
  • 45
  • 59
  • In the example when c is used as a return value, it is copied but what happens to the original c when the function returns and when is the destructor called? – Darrell123 Aug 30 '22 at 14:08
  • @Darrell123 It is NRVO as explained in this [follow up question](https://stackoverflow.com/questions/72383114/does-returning-a-local-variable-return-a-copy-and-destroy-the-originalnrvo?noredirect=1&lq=1). Actually, one of the user had the exact same doubt a while ago(few months) and i recommended them to ask a separate question which they did as can be seen in the above linked question. Moreover, note there was a long discussion here in the comment section which is now deleted by moderators. – Jason Aug 30 '22 at 14:12
5
{ //scope is defined by the curly braces
    std::vector<int> vec;
}
// vec is out of scope here!
vec.push_back(15);
Stuart Golodetz
  • 20,238
  • 4
  • 51
  • 80
Tony The Lion
  • 61,704
  • 67
  • 242
  • 415
3

When it leaves the scope that it was declared in :)

Your question as it stands is not answerable without seeing the implementation. It comes down to where you declare this node.

void Foo()
{
    int i = 10;

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

} // i is out of scope
Ed S.
  • 122,712
  • 22
  • 185
  • 265
2

"Out of scope" is a metonymy: as in, using the name or terminology of one concept to talk about something closely related but different.

In C++ a scope is a static region of program text, and so something "out of scope", taken literally, means physically outside of a region of text. For instance, { int x; } int y;: the declaration of y is out of the scope in which x is visible.

The metonymy "going out of scope" is used to express the idea that the dynamic activation/instantiation of the environment associated with some scope is terminating. And so the variables defined in that scope are going away (thus "out of scope").

What has actually gone "out of scope" is the instruction pointer, so to speak; the program's evaluation is now taking place in a scope which has no visibility to that one. But not everything in a scope goes away! Static variables will still be there the next time the scope is entered.

"Going out of scope" is not very accurate, but short and everyone understands what it means.

Kaz
  • 55,781
  • 9
  • 100
  • 149
2

An object that is declared inside a function (or inside certain curly-brace-bracketed constructs inside functions) falls out of scope when execution leaves that part of code.

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

This only applies to stuff declared on the stack, so it has little to do with singly-linked lists, since list nodes will typically be instantiated on the heap with new.

In this example, the pointer returned by new will go out of scope when the function exits, but nothing will happen to the Node itself:

void make_a_node() {
  Node* p = new Node;
} // Oh noes a memory leak!
DSimon
  • 3,280
  • 2
  • 21
  • 24
  • If the Node class had a destructor implemented would the Node being referenced by p call the destructor the moment p exits the scope? – Pat Murray Apr 09 '12 at 23:13
  • Nope. In my first example, though, the std::string destructor *will* be called when x leaves scope. In the second example, what's leaving scope is not a Node but a Node*, and pointers themselves don't have destructors. – DSimon Apr 10 '12 at 02:07
  • but if instead of a raw pointer you used `std::unique_ptr p = new Node;` the destructor of the Node object would be called on exit, when `p` goes out of scope.. – d7samurai Jan 13 '14 at 01:36