Concepts / Containers of Polymorphic Objects

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.

  • Programming

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

Definition

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.

Key point

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.

callerowns the pointer after thereturnread_shape()builds the objectfree storeholds the Shape object1. call read_shape()2. allocate Shape with new3. return pointer, ownership passes4. scope ends with no delete
When a function returns a raw pointer, at which point does responsibility for deleting the object pass to the caller, and what happens if no one takes it?

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.

cpp
Notes on the code
  • read_shape() allocates the Circle on the free store and returns its address
  • caller() 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.

shapes (vector)index 0pointerCircleheap objectindex 1pointerRectangleheap objectholdsholdspoints topoints to
When the vector holding the pointers is destroyed, which heap objects still exist and which references to them are gone?

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?

  • No, they are destroyed together with the vector

  • Yes, they still exist but nothing can reach them

Reveal answer
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

cpp
Notes on the code
  • Each push_back stores 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
Before
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

After
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

acquiredcreated with newin usereached through a pointerreleaseddeleted by its ownerleakedreserved but unreachablepointer obtainedowner calls deletelast pointer lost, no delete
What states can a dynamically allocated resource move through, and which transition leads to a leak?

Mistakes That Create Leaks

  • Common mistake

    Assuming that destroying a container of pointers releases the objects it holds

    What it looks like

    A vector of Shape pointers goes out of scope while its Circle and Rectangle remain allocated.

    Why it fails

    The container stores addresses, and destroying the container only removes those addresses. The pointed-to objects remain allocated until someone deletes them.

    Do this instead

    Delete 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 like

    caller() calls read_shape(), prints the area, and returns without a delete.

    Why it fails

    Ownership transferred to the caller when the pointer was returned, so the caller is the party responsible for the release.

    Do this instead

    The 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 like

    A function allocates an object, returns early on one branch, and only deletes the object on the other branch.

    Why it fails

    Clear ownership must be enforced at every exit path, and an early return that skips the delete leaves the object unreachable.

    Do this instead

    Make 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.

cpp
Notes on the code
  • 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

cpp
Notes on the code
  • make_shape() transfers ownership of a new Rectangle to its caller
  • run() receives two pointers from make_shape()
EASY

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.

Related concepts

More concepts starting with C