Ownership and Object Lifetime
A local variable's lifetime is tied to a block. A dynamically allocated object's lifetime is tied to a delete call. Code that forgets the delete call keeps the object alive forever, even though nothing can reach it.
A Bug That Never Shows Up on Screen
Some of the most damaging bugs in C++ produce no error message, no crash, and no wrong answer. 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.
What do you think happens?
A function creates an object on the heap, returns a pointer to it, and the caller reads a value from that object and then lets the pointer variable go out of scope without deleting anything. What happens to the object?
Reveal answer
It stays allocated but nothing in the program can reach it anymore.
Losing the pointer without deleting the object causes a leak. The function that created the object has already finished, and the pointer variable was the only route to the object. Going out of scope destroys the pointer variable, not the heap object it pointed to.
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 concrete situations.
Automatic Cleanup Versus Explicit Lifetime
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.
Compare two objects. A local variable declared inside a function begins its life when the declaration is reached and ends when the enclosing block ends. A dynamically created object begins its life when you ask for it with new, and it ends only when you call delete on it. Its life can therefore continue well past the block, and even the function, in which it was created. The object does not care whether any variable still names it; it persists until something explicitly releases it.
A local variable's lifetime is tied to a block. A dynamically allocated object's lifetime is tied to a delete call. Code that forgets the delete call keeps the object alive forever, even though nothing can reach it.
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.
Notice the two halves of that definition. The resource is reserved, so the program's memory use stays high. It is also unreachable, meaning no pointer in the program still holds its address, so no later statement can release it. A leak is therefore not simply a large amount of memory in use. It is memory that the program has lost the ability to give back.
Memory is the resource most often involved in C++ leaks for a structural reason. C++ hands you the explicit tools to request dynamic memory and to release it, and it expects your code to pair those two actions. Other resources exist, but the course's central lesson is about the free store, where every acquisition needs a matching release that the language will not insert for you.
Following a Pointer Returned from a Function
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, in this sense, is the responsibility to call delete on an object exactly once when it is no longer needed.
Consider a function named read_shape() that reads a description from input, constructs a Shape object on the heap, and returns a pointer to it. The function itself does nothing wrong. The danger lies in the caller. The following code is a generated illustration of that situation.
read_shapeallocates the object with new and returns its address; its work ends at the return- The caller receives the only pointer to the object and inherits responsibility for it
- Leaving caller without a delete means the object is still allocated but no pointer reaches it
Read the sequence as a relay. Responsibility for the object is created in step two, passes to the caller in step three, and is never explicitly handed onward or discharged. The last message is the one that matters: the caller uses the object and then drops the only pointer to it. From that point the object is unreachable while still occupying memory, which is exactly the situation the source warns about, where a user might fail to delete the pointer returned by read_shape().
Tracing the Ownership Chain in read_shape
Using the caller() code above, identify at each line who is responsible for releasing the Shape object and whether that responsibility is ever carried out.
- 1Inside read_shape, new Shape()
The callee acquires the resource. Responsibility for releasing it does not yet belong to anyone outside the function, but the callee is about to hand it over.
- 2return s
The callee finishes and returns the address. The caller now holds the only way to reach the object, so the duty to release it passes to the caller.
- 3Inside caller, s->draw()
The caller uses the object. Using it does not discharge ownership; ownership is discharged only by a delete.
- 4End of caller with no delete
The pointer variable s is destroyed, but the Shape object is not. The responsibility was never carried out, so the object is leaked.
The callee was correct to allocate and return the object. The leak is created entirely in the caller, which received ownership and dropped it without calling delete.
- The delete is placed in the caller, the party that received ownership
- The object is released exactly once, on the path that uses it
Containers of Pointers
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.
- Each
push_backstores an address; the vector never creates or owns the Shape objects themselves - The vector's destruction releases the list of addresses, not the objects those addresses name
The diagram shows the failure. The stack-side container and its pointers are gone once the scope ends, but the two Shape objects on the heap are untouched. Each one is reachable only through a pointer that no longer exists, so no statement in the program can delete it. The container cleaned itself up perfectly and the leak still happened, because the container was never responsible for the objects in the first place.
The source describes the owner of such a container as the party who might forget to delete the pointed-to objects. Ownership is the key idea. Someone must be designated as the owner, and that owner must release every element. If the container is passed around, the question of who owns its elements has to be answered as clearly as the question of who owns the container.
- The loop deletes each pointed-to object, which is the step the destroyed vector cannot perform
- The container is cleared afterwards so it no longer holds addresses to objects that have been deleted
Finding Leaks Along Each Branch
A leak often hides on one path through a function rather than on the path you tested. To find it, follow every possible route from the acquisition to the function's exit and ask whether the allocated resource reaches a release point on each one. An early return is the most common place where a route escapes without releasing it.
- The allocation happens before the branch, so every route through the function must release it
- The delete sits only on the path that reaches the end of the function
The flowchart makes the defect visible. The yes branch leaves the function through return, and the Shape acquired at the top is still held by no one. The no branch reaches a delete before the exit. A test that only passes a nonnegative n will always take the release path and will never reveal the leak. This is why the source notes that such a bug can look correct on every test you run.
- Each exit route now discharges the ownership of s before the function ends
Mistakes Beginners Make with Ownership
Common mistake Assuming that a pointer going out of scope deletes the object it points to
What it looks likeShape* s = read_shape(); s->draw(); with no delete, followed by the end of the function
Why it failsOnly the pointer variable is destroyed. The object lives in dynamic memory until delete is called on it.
Do this insteadPair every new-created object with a delete on each path that uses it, or pass ownership explicitly to a party that will delete it.
Common mistake Believing that destroying a container of pointers releases the objects it points to
What it looks likeA std::vector<Shape*> going out of scope after push_back(new Shape())Why it failsThe container stores addresses rather than the objects themselves, so destroying it releases only the addresses.
Do this insteadDelete each pointed-to object explicitly before the container goes away, and make clear which part of the program owns the elements.
Common mistake Checking only the common path when looking for leaks
What it looks likeTesting process(5) and concluding that process has no leak
Why it failsThe early return taken when n is negative never reaches the delete, and the test never exercised it.
Do this insteadTrace every branch and exit, including early returns, from the acquisition to the function's end.
Common mistake Forgetting to document the transfer of ownership at a function interface, so callers do not know they must delete the returned pointer
Spot the Leaks
Read the function below. Identify every path on which the Shape allocated with new is not released, and say which line you would change to fix each one. Then decide whether the function's caller also needs to change.
Shape* make_and_check(int code) {
Shape* s = new Shape();
if (code == 0) {
return nullptr;
}
if (code == 1) {
return s;
}
delete s;
return nullptr;
}Hints
- Trace each of the three return statements and ask whether the Shape has been deleted on that route
- A returned pointer transfers ownership to the caller, so a return that hands s back is not itself a leak; the caller must then delete it
- The nullptr return on the first path discards the only pointer to the object
Key Takeaways
- A resource leak happens when a resource such as dynamically allocated memory 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 a delete causes a leak
- A container of pointers stores addresses rather than objects, so destroying the container does not release the objects; the owner must delete each one explicitly
- Leaks often hide on a single execution path, such as an early return, so trace every path from acquisition to exit rather than only the path you tested
- Clear ownership, documented at function interfaces and enforced at every exit path, is the main defense against resource leaks