Copy Control and Ownership of Resources
A constructor that acquires a resource creates a duty, and the class needs a destructor to release that resource.
Who Gives the Memory Back
Many classes need something from outside themselves to do their job. That might be memory, a file, a lock, or some other limited resource. When a constructor obtains such a resource, the object takes on a responsibility: the resource has to be given back at some point. Before reading on, ask yourself a simple question. If an object grabs a block of heap memory when it is created, who is responsible for returning that memory, and at what moment should it happen?
This article follows that question through the rest of the topic. You will trace what happens to a resource across an object's lifetime, see why a manual release is fragile, and learn the pattern C++ programmers use to make release automatic. Resource management in C++ is largely about meeting this responsibility reliably, including on code paths a programmer might not expect.
Constructors Start a Duty, Destructors End It
Think of a 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 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.
Trace the timeline above with a concrete object in mind. The constructor runs first and takes control of the resource. The object is then used, and during that stretch the resource is available to the code. When the object leaves scope, the destructor runs and gives the resource back. Notice what is absent from this sequence: there is no step where the programmer must remember to release the resource by hand. The release is attached to the object's end rather than to a line of code that might be skipped.
Tracing a Heap Array Through Its Lifetime
The following class is a small example written for this topic. Its constructor acquires an array on the heap, and its destructor releases it. Read it with the earlier rule in mind: the acquisition happens in the constructor, so the release belongs in the destructor.
class IntBuffer {
public:
IntBuffer(int n) : size_(n), data_(new int[n]) { }
~IntBuffer() { delete[] data_; }
int size() const { return size_; }
private:
int size_;
int* data_;
};
int main() {
IntBuffer buf(8);
// buf is used here
return 0;
}- 1Line 3
IntBuffer(int n)acquires an array of n ints on the heap. This is the start of the duty. - 2Line 4 the destructor releases that array. The class's own code now returns what its constructor took.
- 3Line 12 buf is constructed here, which triggers the acquisition.
- 4Line 14 when main reaches its end, buf goes out of scope and its destructor runs automatically, so the caller never writes the release.
- The constructor allocates the array with new, so the object now holds a resource.
- The destructor calls delete[] on the same pointer, matching the acquisition.
- buf is created in main and destroyed automatically when its scope ends.
Following the buffer from creation to release
Using IntBuffer as written above, identify when the heap array is acquired and when it is released.
- 1Creation
IntBufferbuf(8)calls the constructor with n set to 8. The constructor allocates an array of eight ints and stores its pointer in data_. - 2Use
The object holds the array while the code runs. Calls such as
buf.size()work through the object, without any release logic in the calling code. - 3Scope end
When main finishes, buf is destroyed. The destructor calls delete[] on data_, returning the array.
The array is acquired during construction and released during destruction. The release point is determined by the object's lifetime, not by a line the programmer must remember to write.
RAII and Resource Handles
The pattern from the previous sections has a name. RAII stands for Resource Acquisition Is Initialization. The name describes the idea: a resource is acquired during the initialization of an object, and because of that, the object's lifetime controls the resource. The object's destructor performs the release, so ending the object ends the resource's use.
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.
Data flows in two directions through this arrangement. Requests and values travel from the calling code into the handle, and the handle forwards them to the resource. Results travel back the same way. The calling code never needs to know when or how the resource is returned, because the handle owns that decision. In the IntBuffer example, main uses buf and never writes delete; the handle's destructor makes the release happen.
Why Naked new and delete Are Risky
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. That burden is easy to underestimate, which is why the guideline advises avoiding naked new and delete operations.
- new acquires the array, and the matching delete[] is written by hand at the bottom.
- The early return skips the delete, so the array is never released on that path.
Common mistake Assuming the delete at the bottom of a function always runs
What it looks likeAn early return inside report() skips delete[] p entirely.
Why it failsA statement after a return is not reached, so any code placed after an exit is skipped on that path.
Do this insteadHold the resource in an object whose destructor performs the release, so every exit path is covered.
Common mistake Writing a destructor but no matching constructor acquisition, or the reverse
Why it failsThe source's rule ties the two together: a constructor that acquires a resource creates the need for a destructor that releases it.
Do this insteadWhen you write a constructor that acquires something, plan the destructor that returns it at the same time.
Copies That Share One Resource
Once an object owns a resource, copying it raises a new question. Suppose a second object is created from the first and ends up holding the same heap pointer. Each object's destructor now has a reason to release that same array. The destructor rule from earlier says each owner gives its resource back, so two owners pointing at one resource means the release is attempted twice. Draw the picture before reading the explanation: which object owns the memory, and what happens when each destructor runs?
The diagram makes the problem visible. Both objects reach the same array, but neither one is marked as the single owner. When both destructors run, each tries to release the array. The lesson is not a specific fix here, but a question the class must answer: when a resource is held by an object, which object is responsible for giving it back, and can a copy create a second claim on that same resource?
Container Classes and Initializer Lists
The last guideline concerns container classes. A container is a class that holds a collection of elements. The source advises that if a class is a container, it should be given an initializer-list constructor. That 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.
- The braced list {2, 3, 5, 7} is passed to the initializer-list constructor.
- The elements are copied into the container's storage at the moment the object is created.
- The destructor still releases the array, following the same duty as before.
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 does not describe further details of this constructor, so treat this example as showing the general shape: the braces supply the values, and the constructor stores them. Notice that the resource duty from the earlier sections still applies. Because this class acquires heap memory, it still needs a destructor to release it.
Predicting the Release Point
A class Logger has a constructor that opens a file and a destructor that closes it. Inside main, a Logger object is created at the start of a block and the block ends after some work. Without writing any delete or close call in main, where does the file get closed, and why is that a more reliable place than the last line of a function?
Hints
- Ask which operation marks the start of the duty and which marks its end.
- Think about whether an early exit from the block could skip a manual close call.
- The destructor is attached to the object's end, not to a particular line.
Key Takeaways
- A constructor that acquires a resource creates a duty, and the class needs a destructor to release that resource.
- RAII ties a resource's lifetime to an object's lifetime, so the destructor performs the release automatically.
- A resource handle holds a resource on behalf of calling code, which uses the resource through the handle.
- Naked new and delete put the burden of matching every release on the programmer, so the guideline is to avoid them.
- A container class should provide an initializer-list constructor so users can create it already filled with braced values.