When Is an Object "Out of Scope"

When is an object out of scope?

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

Destructor when is this object out of scope

I don't understand when and how e is out of scope

The scope of e is everything from its declaration to the end of its enclosing block scope:

void func()
{ // start of block scope
Entity e; // e created in the enclosing block scope here
e.Print();
} // end of block scope, e (and any other automatic locals) destroyed

but all that only gets executed when I call func in main right?

Now you're mixing up two things:

  1. scope is the section of the source code where e is a valid identifier (and refers to the same object, in case you have other variables with the same name in other code). This is static, it's all known at compile time.
  2. control flow is what when func is actually executed, enters its block scope, executes the statements in the function body, and finally exits block scope (destroying any automatic local objects at the same time). This happens at runtime, and is dynamic (in the sense that it might depend on conditional logic, or input).

So only when I call func in main e gets created and the method gets called but how exactly is it ever out of scope?

It has automatic scope, so its scope ends when the enclosing block does. For e, that's when func has finished executing and before control returns to main.

... When could e ever be out of scope?

Try referring to e in main after the call to func. Does it compile?

No, because e isn't an identifier in this scope.

What's the alternative? Every large program would accumulate every variable name that was ever used in the global namespace. It would rapidly become unmanageable, and in fact that's exactly the reason we use local variables instead of globals.


Note that we can disentangle scope from lifetime just by returning something. If we choose something with dynamic lifetime:

std::unique_ptr<Entity> foo()
{ // block begins
std::unique_ptr<Entity> e; // e scope begins
e.reset( new Entity ); // *e lifetime begins
e->Print();
return std::move(e); // *e moves to return value
} // e destroyed

int main()
{
auto f = foo(); // *e is now called *f
f->Print();
} // f destroyed and deletes *f
  1. You can see there's an anonymous object (created by new), which is pointed at first by *e and then by *f
  2. e has the same (block) scope as before
  3. e has the same (automatic) lifetime, so it is destroyed when it goes out of scope
  4. the object originally known as *e is handed off to f, and keeps existing. It still doesn't have an identifier of its own that could be bound to a scope.
  5. f also has block scope & automatic lifetime
  6. *f (the dynamic object formerly known as *e) is destroyed along with f only because we didn't move it elsewhere.

Why does an object go out of scope when passed as a pointer, but not when it is returned

For makeNode2 the pointer parameter mountPt is passed-by-value itself, then any modification on the pointer itself inside the function like mountPt = n; has nothing to do with the original argument origin.

You can change it to pass-by-reference, i.e.

void makeNode2(int value, Node*& mountPt)

When do references of objects go out of scope?

In .NET it is very important to understand the difference between value types and reference types. Value types are mostly primitive types that are allocated on the stack. This memory is freed once the method is exited. This is the case in your first sample (Integer is a value type).

In contrast, classes are reference types. This applies to both the Form and MyRandomClass. For reference types, the memory is allocated on the managed heap. Only the reference to the memory that points to the location on the heap is allocated on the stack. If all references to an object on the heap go out of scope, the memory on the heap is also freed by the Garbage Collector at a later point in time.

This is what happens in your 3rd sample: you allocate an object on the heap with a single reference named ob pointing to it. At the end of the method, the reference ob is freed, but the object on the heap will still exist until it is deallocated by the Garbage Collector.

However, your second sample is indeed a special case. You create a new instance of Form1 and store the reference in frm1. When calling Show the form becomes a part of the user interface of the application. As it is a non-modal form, it is shown on the screen but the execution of your application does not wait until the form is closed. The user interface thread of your application is used to handle the messages for the form until it is closed. So there are some other references to the form though the one you used to create it initially has gone out of scope already. After the form has been closed, the memory is freed as there are no active references to it anymore.



Related Topics



Leave a reply



Submit