Concepts / Resource Management and Scope

Resource Management and Scope

Objects on the free store are not released automatically at scope exit, so the standard library provides smart pointers to help manage them.

  • Programming

Why Scope Alone Is Not Enough

When a function ends, the local variables declared inside it go away, and the resources they hold are released with them. Programmers rarely have to think about this, because it happens automatically at the edge of the scope. Objects created on the free store are different. They are not tied to a scope in the same way, so the question of who releases them, and when, needs an explicit answer.

What do you think happens?

A function creates an object with new, stores its address in a local pointer, and then returns. Does the object the pointer pointed to get released when the pointer goes out of scope?

  • Yes, the object is released when the local pointer goes out of scope

  • No, the object lives on the free store and is not released automatically at scope exit

Reveal answer
Answer:

No, the object lives on the free store and is not released automatically at scope exit.

The pointer is a scope-bound variable and disappears at scope exit, but the object it pointed to is not tied to that scope. Nothing releases it automatically, so the program must arrange for that release. That gap is the reason smart pointers exist.

Scope-Bound Objects and Free Store Objects

Objects defined in a scope already have their resources released when the program exits that scope. That handles a large group of objects automatically. The harder question is what happens to objects allocated on the free store, because they are not tied to a scope in the same way. The diagram below separates the two situations: the pointer is a local name that lives inside the scope, while the object it refers to sits on the free store and outlives that scope unless something releases it.

Function scopelocal variablesPointer premoved at scope exitObject on free storestill exists after scopeexitcontainspoints to
Where does the pointer live, where does the object it points to live, and what remains after the scope ends?

Read the diagram as two lifetimes. The pointer's lifetime ends with the scope, while the object's lifetime is not set by the scope at all. If nothing else releases the object, it stays allocated after the pointer is gone, and the program has lost the only name it had for it. This is the situation that smart pointers are designed to prevent.

How Memory Leaks Happen

A memory leak occurs when memory that a program obtained is no longer released, typically because the program lost track of it. The source attributes leaks to careless programming. The cause is rarely a single bad line. It is usually a path through the code that reaches the end of a function, or leaves it early, without ever reaching the release.

cpp
Notes on the code
  • The new expression obtains memory from the free store.
  • The early return skips every line after it, including the delete[].
  • When fail is true, the memory is no longer released, and the pointer that tracked it is gone.

The flowchart traces which paths reach the delete call. Only one of the two outcomes releases the memory. The early return is the path that leaks, even though the programmer wrote a correct delete for the normal case.

Allocate with newEarly return?Returnmemory leakedReach deleteMemory releasedyesnodelete
Which execution paths reach the delete call, and which paths such as an early return skip it and leak the memory?

Smart Pointers as Owners

The standard library provides two smart pointers, declared in the memory header, for managing objects on the free store. The names describe the ownership model. A unique_ptr is meant to be the only owner of the object it manages. A shared_ptr is meant to be one of several owners sharing the same object. The most basic use of either one is to prevent memory leaks caused by careless programming, and choosing between them depends on whether one owner or several will manage the object.

unique_ptrManaged objectshared_ptr AManaged objectshared_ptr Bonly ownerco-ownerco-owner
How many owners can point to the same object in each case, and what happens to the object when each owner goes away?

Destructors Complete the Constructor's Duty

A class often needs something from outside itself to do its job: memory, a file, a lock, or some other limited resource. When a constructor obtains such a resource, it takes on a responsibility. The resource has to be given back at some point, and the class is the natural place for that to happen. The source's guideline is direct: if a constructor acquires a resource, the class needs a destructor to release it. Without that destructor, nothing in the class's own code returns the resource, and it stays held after the object that needed it is gone.

cpp
Notes on the code
  • The constructor marks the start: the object takes control of the array.
  • The destructor marks the end: the object gives the array back.
  • Between those two points the object can use the array freely.

Notice what the code does not contain: any step where the programmer must remember to release the array by hand. The release is attached to the object's end, not to a line that a particular path might skip. Any code that creates a Buffer and lets it go out of scope gets the release automatically.

RAII and Resource Handles

The source recommends resource handles and RAII to manage resources. RAII stands for Resource Acquisition Is Initialization. The name describes the pattern: a resource is acquired during initialization of an object, and because of that, the object's lifetime controls the resource. The object's destructor performs the release. A resource handle is an object whose job is to hold a resource on behalf of the code that uses it. The handle acquires the resource when it is created and releases it when it is destroyed, so the programmer's attention can stay on the work rather than on bookkeeping.

Calling codeResource handleResourcecreate handleacquireuseuseleave scope earlyrelease
In what order do the constructor acquire and the destructor release happen as control enters and leaves a scope, including on early exit?

Read the sequence from top to bottom. Acquisition happens when the handle is created, before any use. Use happens in the middle, and data flows in both directions: requests and values travel from the calling code into the handle, which forwards them to the resource, and results travel back the same way. The release comes last, and it happens even when the calling code leaves early. The calling code never needs to know when or how the resource is returned, because the handle owns that decision.

Worked example

Tracing a handle through an early exit

A function creates a Buffer handle, then returns early before the function's normal end. When does the array get released?

  1. 1
    Create the handle

    The Buffer constructor runs and acquires the array. The resource now belongs to the handle.

  2. 2
    Leave early

    The early return ends the function's scope before the final line runs.

  3. 3
    Destroy the handle

    The Buffer destructor runs as the object's scope ends, and it releases the array.

Result

The array is released on the early path too, because the release is tied to the object's lifetime rather than to a line the function reaches.

Naked new and delete Versus Managed Alternatives

The source advises avoiding naked new and delete operations. A naked new or delete is one that appears directly in ordinary code, with nothing managing the resource it produces. Every naked new must be matched by a delete somewhere else, and the programmer must make sure that match happens on every path through the code. Naked new and delete put the burden of matching every release on the programmer, which is easy to underestimate. A managed alternative, such as a smart pointer or a resource handle, takes that burden over.

AspectNaked new and deleteManaged alternative
Who matches each release?The programmer, on every pathThe owning object, handled automatically
Early return or exceptionCan skip the delete and leakThe owner's release still happens
Number of ownersNot stated in the codeunique_ptr for one owner, shared_ptr for several
Main riskForgotten or mismatched deleteChoosing the wrong ownership model

Manual cleanup compared with a managed alternative

Initializer-List Constructors for Containers

The last guideline in this section concerns container classes. A container is a class that holds a collection of elements. The source says that if a class is a container, it should be given an initializer-list constructor. This constructor accepts a list of values written in braces, so a user can create the container already filled with those values. This makes the container easier to create and read, and it matches the way programmers commonly write small collections of values.

cpp
Notes on the code
  • The braced list 1, 2, 3 is passed to the constructor as the initializer list.
  • The container is populated at the moment it is created.
  • This is a general sketch; the source does not describe further details of this constructor.

Common Mistakes with Resource Management

  • Common mistake

    Assuming an object allocated with new is released when its pointer goes out of scope

    What it looks like

    A function stores the result of new in a local pointer and returns without any delete.

    Why it fails

    Objects on the free store are not released automatically at scope exit. Only the pointer disappears.

    Do this instead

    Give the object an owner, such as a unique_ptr, or make sure a matching delete runs on every path.

  • Common mistake

    Forgetting the delete on an early return

    What it looks like

    The function calls new, returns early on an error, and reaches delete only on the normal path.

    Why it fails

    The early return skips the delete, so the memory is leaked.

    Do this instead

    Use a smart pointer or a resource handle so the release does not depend on which path runs.

  • Common mistake

    Writing a constructor that acquires a resource with no destructor

    What it looks like

    A class allocates an array in its constructor and never defines a destructor.

    Why it fails

    Nothing in the class's own code returns the resource, so it stays held after the object is gone.

    Do this instead

    Add a destructor that releases the resource the constructor acquired.

  • Common mistake

    Using shared_ptr when there is only one owner

    What it looks like

    A single function creates a shared_ptr and never shares it with anything.

    Why it fails

    shared_ptr represents shared ownership. When one owner is clear, the ownership model does not match the design.

    Do this instead

    Use unique_ptr when a single owner manages the object.

  • Common mistake

    Writing naked new and delete in ordinary code instead of relying on a managed alternative

Practice Tracing Resource Lifetimes

MEDIUM

A function allocates an object with new, performs a check, and returns early when the check fails. The delete is placed only at the end of the function. Identify the path that leaks memory, then describe two different ways to fix it using ideas from this article.

Hints
  • Trace each path from the allocation to the end of the function, including the early return.
  • Ask which path reaches the delete, and which path leaves without reaching it.
  • A smart pointer or a resource handle ties the release to an owner rather than to a particular line.

Key Takeaways

  • Objects on the free store are not released automatically at scope exit, so the program or a managing object must release them.
  • Memory leaks come from careless paths through code, such as early returns, and smart pointers help prevent them by giving each object a clear owner.
  • unique_ptr represents unique ownership with one owner, while shared_ptr represents shared ownership among several owners; choose based on how many owners will manage the object.
  • A constructor that acquires a resource needs a destructor that releases it, and RAII ties the resource's lifetime to the object's lifetime through resource handles.
  • Naked new and delete put the burden of matching every release on the programmer, so a managed alternative is preferred, and a container class should provide an initializer-list constructor.

Related concepts

More concepts starting with R