Concepts / RAII and Scope-Based Resource Management

RAII and Scope-Based Resource Management

A resource handle is an object that manages a resource, and it is distinct from the resource itself.

  • Programming

Why Resources Need a Responsible Owner

A program often obtains a resource, such as memory, a file, or some other limited item, and must later give it back. If the program forgets to give it back, or gives it back in the wrong place, the resource stays in use longer than it should. This article is about a way of making that obligation reliable, so that the programmer does not have to track every release by hand.

Managing that obligation by hand is error-prone. The code path that acquires a resource and the code path that releases it can be far apart in the program, and early exits can skip the release entirely. The approach taught here is to let an object carry the obligation, so that the release is tied to the object's lifetime instead of to a line of code that might be missed.

Tracing a Handle From Declaration to Scope Exit

Consider a resource handle declared inside a block. A resource handle is an object whose job is to manage a resource, and it is distinct from the resource itself. Its lifecycle has a clear sequence of states. First, the handle is created, and as part of that creation it acquires the resource it manages. Second, the program works with the resource through the handle for as long as the handle exists. Third, the program leaves the block, the handle goes out of scope, and it is destroyed. Fourth, that destruction releases the resource.

Handle declaredinside a blockResource acquiredduring creation of thehandleResource usedthrough the handleScope exitprogram leaves the blockHandle destroyedhandle goes out of scopeResource releasedcaused by the destructioncreationhandle existsblock endshandle leaves scopedestruction
When is the resource acquired and released relative to the handle's declaration, use, and scope exit?

Read the timeline from left to right. The acquisition happens at the start, when the handle is created, and not at some later line the programmer has to remember. The release happens at the end, when the handle is destroyed as its scope ends. Tracing the handle's lifetime from creation through scope exit therefore tells you exactly when the resource is given back.

Key point

The practical payoff is that release becomes a consequence of the handle's lifetime ending. The programmer does not need a separate release statement at every exit point, because the handle's lifetime already determines when the release happens.

How RAII Ties Release to Destruction

Definition

RAII stands for Resource Acquisition Is Initialization. Under this idiom, a resource is acquired during the initialization of an object, and because of that, the object's lifetime controls the resource. The acquisition is part of creating the handle, and the release follows from the handle's destruction when its scope ends.

The name describes the pattern in three parts. Acquisition happens at creation. Initialization is the moment the object comes into existence. Because the object's lifetime is what governs the resource, the object's destructor is the place where the release is performed. The source material puts the idea in one sentence: RAII ties a resource's lifetime to an object's lifetime, so the object's destructor performs the release.

A resource handle is the object that puts this idiom to work. Its job is to hold a resource on behalf of the code that uses it. The calling code uses the resource through the handle, while the handle keeps the responsibility for the resource. The diagram below shows the two separate things involved: the handle, which lives on the stack, and the resource it owns, which lives elsewhere.

Calling codedeclares and uses thehandleResource handlelives on the stackManaged resourcelives elsewhereuses throughholds and releases on destruction
How is the handle object related to the resource it owns, and which of the two is responsible for releasing it?

The handle is the piece responsible for releasing the resource. The calling code never needs to know when or how the resource is returned, because the handle owns that decision. Requests and values travel from the calling code into the handle, which forwards them to the resource, and results travel back the same way.

A Constructor That Takes On a Duty

A class often needs something from outside itself to do its job, such as memory, a file, a lock, or another 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 states the guideline directly: 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.

Think of the resource as having two boundaries. The constructor marks the start: this is where the object takes control of the resource. The destructor marks the end: this is where the object gives the resource back. Between those two points, the object can use the resource freely. The sequence below shows the order of these events.

Calling codeactorHandleconstructor and destructorResourceheld by the handle1. declare handle2. constructor acquires resource3. use through handle4. scope ends5. destructor releases resource
In what order do the calling code, the handle, and the resource interact, and where is the resource acquired and released?

Notice what the sequence does not contain: any step where the programmer must remember to release the resource by hand. When the destructor is written, the release is attached to the object's end rather than to a line of code that might be skipped. The following class is a small example written for this article. Its constructor acquires an array on the heap, and its destructor releases it. The acquisition happens in the constructor, so the release belongs in the destructor.

Code walkthrough
class IntBuffer {
public:
    IntBuffer(int n) : data_(new int[n]), size_(n) {}
    ~IntBuffer() { delete[] data_; }
    int size() const { return size_; }
private:
    int* data_;
    int size_;
};

int main() {
    IntBuffer buf(8);
    // use buf.size() and the heap array
    return 0;
}
  1. 1
    IntBuffer(int n) runs when buf is declared and acquires an array of n integers on the heap.
  2. 2
    The member initializer stores the pointer in data_ and the size in size_.
  3. 3
    ~IntBuffer() runs automatically when buf goes out of scope and calls delete[] on data_.
  4. 4
    main never releases the array itself, because the object's destructor is responsible for that duty.
  • The constructor acquires the heap array with new
  • The destructor releases that same array with delete[]
  • The calling code never writes a delete statement

Early Returns and Exceptions

The benefit of tying release to destruction matters most when a function does not follow its ordinary path. A function may return early, or an exception may be raised between the acquisition and the point where the programmer would normally release the resource. The flowchart below follows both branches. Notice that the managed path always ends with the destructor running, while the raw pointer path can end without any release at all.

Acquire resourcein handle constructorWork with resourcethrough the handleEarly return orexception?Handle destructorrunsduring scope exitResource releasedNormal end of scopeRaw pointer pathno destructor to runRelease line skippedresource stays heldyes, handle pathnoscope exityes, raw pointer path
If a function returns early or throws between acquisition and release, what happens to the resource in each case?

Naked new and delete Versus a Managed Handle

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. The burden of matching every release is placed on the programmer, which is why the guideline exists and why that burden is easy to underestimate.

Naked newallocation in ordinary codeMatching deletemust be written on everypathException skipsdeleteleak possibleHandle constructedacquisition in creationDestructor runson scope exitResource releasedno separate release linerequiresmay be skippedscope endsreleases
When an exception occurs between allocation and deallocation, how does cleanup differ, and where can the leak happen?

The managed alternative provides a handle whose acquisition and release are both attached to the object. The programmer writes the acquisition as part of creating the handle and does not write a matching release at each exit. The leak point in the naked version, the place where a delete can be skipped, does not exist in the handle version, because the release is driven by the handle's destruction.

Mistakes That Undermine Handle Management

  • Common mistake

    Writing a constructor that acquires a resource but no destructor to release it

    What it looks like

    A class whose constructor allocates an array with new but which has no 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

    Write a destructor that releases the resource the constructor acquired, as the IntBuffer example does.

  • Common mistake

    Relying on a separate release statement at the end of a function

    What it looks like

    Calling delete on a pointer near the bottom of a function body

    Why it fails

    An early return or an exception can jump past that line, so the release is skipped.

    Do this instead

    Let the handle's destructor perform the release, so it runs when the scope ends on any path.

  • Common mistake

    Assuming the calling code must release the resource

    What it looks like

    Calling code that expects to issue the release itself after using a handle

    Why it fails

    The source makes the handle responsible for release, so the calling code's job is to use the resource through the handle.

    Do this instead

    Trace the handle's lifetime and expect the release at its destruction.

  • Common mistake

    Declaring a handle in a wider scope than needed, so the resource is held longer than the work requires

  • Common mistake

    Confusing the handle with the resource it manages, when the handle is the object that owns the release

Debugging

A function acquires a resource, then has a branch that returns before the release line. The resource stays in use after the function exits. Tracing the path through the branch shows that the release was never reached. Rewriting the resource as a handle moves the release into the handle's destructor, so the early return no longer matters.

Initializer-List Constructors for Containers

The last guideline in this material 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. An initializer-list 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 supplies the elements all at once
  • The container is populated at the moment it is created

In this sketch the container's elements come straight from the braced list, so the object is populated at the moment it is created. The source pack does not describe further details of this constructor, so this example shows only the general shape of its use and makes no further claims about how it is implemented.

Practice Tracing a Handle's Lifetime

EASY

A block declares a resource handle, then uses it, then leaves the block. Write out the order of four events: the handle is created, the resource is acquired, the resource is released, and the handle is destroyed. Then say which event would move if the programmer added an early return between the use and the end of the block.

Hints
  • Acquisition is part of creating the handle, so it happens at declaration.
  • Release follows from the handle's destruction when its scope ends.
  • An early return still leaves the block, so the handle is still destroyed on the way out.
MEDIUM

Explain, in your own words, why a class whose constructor acquires a resource needs a destructor that releases it, and what would happen to the resource if the destructor were missing.

Hints
  • Think about who gives the resource back.
  • Consider what remains held after the object is gone.

Key Takeaways

  • A resource handle is an object that manages a resource, and it is distinct from the resource itself.
  • Under RAII, resource acquisition happens during creation of the handle, and release follows from the handle's destruction when its scope ends.
  • Tracing a handle from declaration through scope exit tells you exactly when its resource is given back, including on early returns and exceptions.
  • A constructor that acquires a resource creates a duty, so the class needs a destructor that releases it; naked new and delete put the burden of matching every release on the programmer.
  • A container class should provide an initializer-list constructor so that a user can create it already filled with a braced list of values.

Related concepts

More concepts starting with R