Resource Management in Classes
When you write one of these functions yourself, re-check the others. Do not assume that a compiler-generated version of a function you did not write is still there and still does what your class needs.
A Class That Takes Ownership of Something
A class often needs something from outside itself to do its job. That something might be 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. Resource management in C++ is largely about meeting that responsibility reliably, including on code paths a programmer might not expect.
What do you think happens?
Will the compiler supply an ordinary constructor for a class you write with no constructor?
Reveal answer
No, it does not generate an ordinary constructor. The compiler can generate implementations of several special member functions as needed, but an ordinary constructor is the exception.
This is why the practical question is not whether a class has a member function at all, but whether each needed special member function has been explicitly defined.
Special Member Functions the Compiler Can Generate
Special member functions are the functions a class can have that govern how its objects are created, copied, moved, and destroyed. The source names six of them: the default constructor, the destructor, the copy constructor, the copy assignment operator, the move constructor, and the move assignment operator.
- Default constructor
- Destructor
- Copy constructor
- Copy assignment operator
- Move constructor
- Move assignment operator
The compiler can generate default implementations for these functions as needed, so you do not always have to write each one yourself. There is one important exception: the compiler does not generate an ordinary constructor for you. A class that needs a constructor to set up its state must provide one.
How Explicit Definitions Change the Defaults
Defining some of these functions yourself affects which other defaults the compiler will produce. The source states that explicitly defining some defaults prevents other default definitions from being generated. So the question to ask is not simply whether a class has a given member function, but whether each special member function the class actually needs has been explicitly defined.
When you write one of these functions yourself, re-check the others. Do not assume that a compiler-generated version of a function you did not write is still there and still does what your class needs.
Constructors That Acquire and Destructors That Release
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 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.
The following class was written for this article. Its constructor acquires an array on the heap, and its destructor releases it. Notice that the acquisition happens in the constructor, so the release belongs in the destructor.
class ResourceBuffer {
public:
ResourceBuffer(int n) : size_(n), data_(new int[n]) {
}
~ResourceBuffer() {
delete[] data_;
}
int size() const { return size_; }
private:
int size_;
int* data_;
};- 1The constructor initializes size_ and allocates an array of n ints on the heap, storing its location in data_. This is the start boundary.
- 2The destructor runs when the object ends, and delete[] gives the array back. This is the end boundary.
- 3Code that uses a
ResourceBuffernever has to call delete itself. The release is attached to the object's end.
- The constructor acquires the heap array with new[]
- The destructor releases the same array with delete[]
Tracing a Buffer's Lifetime
Follow one ResourceBuffer through its life. Each stage below corresponds to one point on the timeline. The important observation is that the release is tied to the object's end rather than to a line the programmer might skip.
Read the timeline from left to right. The object is created, the constructor acquires the array, and the code works with it. When the scope ends, whether by reaching its closing brace or by leaving early, the destructor runs and releases the array. The programmer does not write a separate release step at the point of exit, which is why an early exit does not leave the array held.
Pointer Members and Default Copying
A pointer or reference member calls for careful, explicit decisions about copying and moving. The source advises considering copy and move behavior in light of ownership, and documenting any resource that is owned outside the class. The reason is that the compiler's default copy copies the pointer value itself, not the block of memory the pointer refers to.
- The copy shares one heap array between two objects
- The second delete[] targets memory that was already released
After the default copy runs, two objects point to the same heap block. Each object's destructor then tries to delete that block. The first delete releases the resource, and the second acts on memory that has already been given back. This is the double delete problem that a careful copy decision is meant to prevent.
Owned Versus Externally Owned Pointers
Not every pointer member is the same. Some point to a resource the class must delete, and some point to memory that lives outside the class and is managed by someone else. Before deciding how a class should copy, you have to know which kind each pointer is. The table below contrasts the two cases.
| Question | Pointer to a resource the class must delete | Pointer to externally owned memory |
|---|---|---|
| Who deletes the memory? | The class, in its destructor | Someone outside the class |
| Is default copying safe? | Not safely; two objects would each try to delete the block | Can be reasonable, because the class does not release the memory |
| What should the code do? | Make an explicit copy or move decision based on ownership | Document that the resource is externally owned |
Deciding how a pointer member affects default copying
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. The source explains that this burden is easy to underestimate, which is why the guideline is to avoid naked new and delete operations.
Common mistake Assuming the programmer's own delete will always run
What it looks likeA function allocates with new, then returns early before reaching the matching delete.
Why it failsA naked delete is only useful if the code path reaches it. Every path through the code has to be checked.
Do this insteadAttach the release to an object that cleans up automatically, such as a resource handle.
Common mistake Writing a constructor that acquires a resource but no destructor
What it looks likeA constructor calls new[] and the class has no destructor.
Why it failsNothing in the class's own code returns the resource, so it stays held after the object is gone.
Do this insteadWrite a destructor that releases the resource the constructor acquired.
RAII and Resource Handles
The source recommends using resource handles and RAII to manage resources. RAII stands for Resource Acquisition Is Initialization. The name describes the pattern: 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.
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. The programmer's attention can therefore stay on the work rather than on bookkeeping.
Data flows in two directions through a handle. Requests and values travel from the calling code into the handle, which 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.
Initializer-List Constructors for Containers
A container is a class that holds a collection of elements. The source says that a container class should be given an initializer-list constructor. This kind of 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 is accepted by the initializer-list constructor
- The elements are placed into the container's storage at creation
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 the example shows only the general shape.
Practice: Tracing Ownership
A class's constructor allocates an array with new[] and stores the pointer in a member. Which special member function must release that array, and what goes wrong if the class uses the compiler's default copy and both objects are destroyed?
Hints
- Look for the function that marks the end of the object's life
- Think about what the default copy duplicates: the pointer or the array
- Ask how many destructors will try to release the same block
Key Takeaways
- The compiler can generate default special member functions as needed, but it does not generate an ordinary constructor, so a needed constructor must be written explicitly.
- A constructor that acquires a resource creates a duty: the class needs a destructor that releases it, and RAII ties the resource's lifetime to the object's lifetime.
- The compiler's default copy duplicates a pointer, so a class that owns a heap resource must make an explicit copy or move decision, while externally owned memory should be documented.
- Naked new and delete put the burden of matching every release on the programmer, so resource handles that release automatically are the preferred approach, and containers should provide an initializer-list constructor.