Skip to Content
Course content

51: Avoiding Memory Leaks and Dangling Pointers

Click on the "Edit" button in the top corner of the screen to edit your slide content.

How do I stop a pointer from pointing to garbage after I've deleted the object?

This is the classic "dangling pointer" problem. When you call delete ptr;, you're telling the operating system, "I'm done with the memory at this address." However, the variable ptr itself doesn't magically disappear or reset; it still holds that memory address. If you accidentally try to use ptr again, you're accessing memory you no longer own. This might crash immediately, or worse, it might "work" for a while and then corrupt your data randomly.

The simplest fix is a habit: immediately set the pointer to nullptr after deleting it.

GameCharacter* player = new GameCharacter("Aragorn");
// ... some game logic ...
delete player; 
player = nullptr; // Now it's safe.

if (player != nullptr) {
    player->attack(); // This block won't execute, preventing a crash.
}

I've spent hours debugging "heisenbugs" because I forgot this one line. In C++, checking for nullptr is your primary safety net when dealing with raw pointers.

I keep forgetting to call delete. Is there a way to make the compiler handle the cleanup?

Yes, and in modern C++, this is exactly how we avoid memory leaks. You should rarely be using new and delete manually. Instead, we use Smart Pointers. The most common one is std::unique_ptr. It uses a concept called RAII (Resource Acquisition Is Initialization), which basically means the memory is tied to the lifetime of the pointer object itself.

Think of it like a contract: when the unique_ptr goes out of scope (like at the end of a function), it automatically calls delete for you. No more forgetting to clean up.

#include <memory>

void spawnEnemy() {
    // No 'new' keyword needed here if we use make_unique
    std::unique_ptr<Enemy> monster = std::make_unique<Enemy>("Goblin");
    monster->roar();
    
    // No delete needed! Memory is freed automatically when the function ends.
}

If you need multiple parts of your code to "share" ownership of an object, you can use std::shared_ptr. It keeps a reference count and only deletes the object when the last pointer to it is destroyed. Just be careful—if two shared_ptrs point to each other, you get a circular reference, and you're right back to having a memory leak.

What actually happens if I delete the same pointer twice?

This is called a "double free," and it's a recipe for a security vulnerability or a hard crash. When you call delete, the memory manager marks that block as available. If you call delete on that same address again, you're trying to free memory that might have already been reassigned to a completely different part of your program.

Here is the scary part: delete nullptr; is perfectly safe and does absolutely nothing. This is why the player = nullptr; trick I mentioned earlier is so powerful. If you accidentally call delete on a pointer that has already been deleted and nulled, the program just keeps running smoothly.

GameCharacter* boss = new GameCharacter("Smaug");
delete boss;
// delete boss; // CRASH! Double free.

boss = nullptr;
delete boss; // Perfectly safe. No crash.



📋 Practical Task

Fixing the Leaky Game Inventory System

You've been handed a piece of legacy code for a game inventory system. The current implementation uses raw pointers and has a massive memory leak because items are added to the inventory but never deleted when the inventory is cleared. Additionally, there is a risk of dangling pointers when items are removed.

Your Task:

  • Refactor the Inventory class to use std::unique_ptr<Item> instead of raw Item* pointers.
  • Remove the manual delete calls in the destructor, as the smart pointers will now handle this automatically.
  • Ensure that the removeItem method properly handles the memory release (which should now happen automatically when the unique_ptr is removed from the container).

Starter Code:

#include <iostream>
#include <vector>
#include <string>
#include <memory>

class Item {
public:
    std::string name;
    Item(std::string n) : name(n) { std::cout < "Created " < name < "\n"; }
    ~Item() { std::cout < "Destroyed " < name < "\n"; }
};

class Inventory {
    std::vector<Item*> items; // Change this to unique_ptr
public:
    void addItem(std::string name) {
        items.push_back(new Item(name)); // Refactor to make_unique
    }

    void clearInventory() {
        for (auto item : items) {
            delete item;
        }
        items.clear();
    }

    ~Inventory() {
        clearInventory();
    }
};
Rating
0 0

There are no comments for now.

to be the first to leave a comment.