Containers of Polymorphic Objects
A leaked object is still occupying memory, but no pointer in the program can reach it anymore. Nothing can ever delete it, so the memory stays reserved for as long as the program runs.
A Leak That Produces No Error
Some of the most damaging bugs in C++ produce no error message, no crash, and no wrong answer on the screen. The program appears to work correctly for every test you run, yet its memory use keeps creeping upward each time it executes a particular path. Something was obtained and never given back. That pattern is a resource leak, and it is worth understanding before you write any code that asks for memory at runtime.
As you read this article, keep one question in mind: at each point in the program, who is responsible for releasing the memory, and has that responsibility actually been carried out? The rest of the article traces that question through two concrete situations: a pointer returned from a function, and a container that stores pointers.
What a Resource Leak Is
A resource leak happens when a resource, such as dynamically allocated memory, is acquired but never released, leaving it reserved yet unreachable.
Memory is the resource this course focuses on, because C++ hands it to you explicitly. When you ask for an object to be created dynamically, the object lives in dynamically allocated memory until you release it yourself. Nothing reclaims it automatically at the end of a block the way a local variable is cleaned up. That explicit lifetime is the source of both the power and the danger.
A leaked object is still occupying memory, but no pointer in the program can reach it anymore. Nothing can ever delete it, so the memory stays reserved for as long as the program runs.
Who Owns a Returned Pointer
Functions often build objects and hand them back to the caller as pointers. The function creates the object in dynamic memory, returns the address, and then its own work is finished. Whoever received that address now holds the only way to reach the object. The caller has inherited the duty to release it.
Ownership therefore moves at a specific moment: the instant the function returns the pointer. Before the return, the function is responsible for the object. After the return, the caller is. The diagram below shows the order of those events for a function named read_shape() that builds a Shape on the heap.
Tracing the read_shape Caller
The function itself does nothing wrong. The danger lies entirely in the caller. The example below is a simplified version of the situation the source describes, in which a caller uses the returned pointer and then lets it go out of scope without deleting the object. The example is an original illustration written for this article; Shape is assumed to be a base class with derived classes such as Circle.
read_shape()allocates the Circle on the free store and returns its addresscaller()receives the address and becomes responsible for releasing the Circle- The closing brace ends the lifetime of the pointer variable s, not the Circle it points to
The fix is to make the release explicit. The caller must delete the object before its pointer goes away, and it must do so on every path through the function. Clear ownership documented at the function interface and enforced at every exit path is the main defense against resource leaks.
Containers That Hold Addresses
A second opportunity for a leak appears when objects are stored in a container through pointers. A container of Shape pointers holds addresses, not the shapes themselves. Removing the pointers from the container, or destroying the container, does not by itself release the objects they refer to. Those objects remain allocated until somebody explicitly deletes them.
This is why a container of polymorphic objects needs extra care. Each element is a pointer that refers to a separate object on the heap, and the container is only the set of those pointers. The diagram shows a vector with two slots, each pointing to a heap object. When the vector is destroyed, its slots disappear, but the objects in the heap region do not.
What do you think happens?
After build_scene() below finishes and its vector is destroyed, do the Circle and the Rectangle still exist in memory?
Reveal answer
Yes, they still exist but nothing can reach them.
The vector stored only addresses. Destroying it removes the addresses, not the objects they referred to, so both objects remain allocated and unreachable until someone deletes them.
Following Ownership Through the Container
- Each
push_backstores a new pointer; no object is copied into the vector - The vector owns the pointers but not the objects they refer to
- Nothing in this function ever calls delete
void build_scene() {
std::vector<Shape*> shapes;
shapes.push_back(new Circle(1.0));
shapes.push_back(new Rectangle(2.0, 3.0));
}Two heap objects are allocated; the vector is destroyed on return, leaving both objects unreachable
void build_scene() {
std::vector<Shape*> shapes;
shapes.push_back(new Circle(1.0));
shapes.push_back(new Rectangle(2.0, 3.0));
for (Shape* s : shapes) {
delete s;
}
shapes.clear();
}Each heap object is deleted through its pointer before the vector is cleared, so no object is left unreachable
Mistakes That Create Leaks
Common mistake Assuming that destroying a container of pointers releases the objects it holds
What it looks likeA vector of Shape pointers goes out of scope while its Circle and Rectangle remain allocated.
Why it failsThe container stores addresses, and destroying the container only removes those addresses. The pointed-to objects remain allocated until someone deletes them.
Do this insteadDelete each pointed-to object explicitly, as part of an agreed ownership plan, before the container is discarded.
Common mistake Using the pointer returned by a function and never deleting the object
What it looks likecaller() calls read_shape(), prints the area, and returns without a delete.
Why it failsOwnership transferred to the caller when the pointer was returned, so the caller is the party responsible for the release.
Do this insteadThe caller must delete the object before its last pointer goes out of scope.
Common mistake Deleting the objects on the main path but not on an early exit
What it looks likeA function allocates an object, returns early on one branch, and only deletes the object on the other branch.
Why it failsClear ownership must be enforced at every exit path, and an early return that skips the delete leaves the object unreachable.
Do this insteadMake sure the release happens on every path through the function.
Common mistake Losing track of who owns a container's elements when the container is passed to another function
Leaks That Depend on Control Flow
A leak does not require a long or complicated program. It can come from a single early return. In the original illustration below, the object is allocated at the top of the function, and it is deleted only if the function reaches the end. When skip is true, the function returns first and the object becomes unreachable.
- The new expression acquires the resource before any branch is taken
- The early return ends the function without reaching the delete
- Only the path through the end of the function releases the Circle
Finding the Leak in Short Code
make_shape()transfers ownership of a new Rectangle to its callerrun()receives two pointers frommake_shape()
In the code above, which object is leaked, and at what point does its last pointer become unreachable? Explain your answer using the idea of ownership transfer from a returned pointer.
Hints
- Ask who owns each pointer returned by
make_shape()as soon as the call returns. - Check whether every object obtained with new has a matching delete by the end of
run().
Key Takeaways
- A resource leak occurs when a dynamically allocated resource is acquired but never released, leaving it reserved yet unreachable.
- A function that returns a pointer to a free-store object transfers responsibility for releasing it to the caller, and losing that pointer without deleting the object causes a leak.
- A container of pointers stores addresses rather than objects, so destroying the container does not release the objects it points to; the owner must delete each one explicitly.
- Clear ownership, documented at function interfaces and enforced on every exit path, is the main defense against resource leaks.