DeveloperBreeze

Overview

C++ developers often face memory management headaches, especially when working on legacy systems that don’t use C++11 or newer. Smart pointers like std::unique_ptr and std::shared_ptr are powerful, but what if you’re stuck with raw pointers?

In this tutorial, you'll learn:

  • How memory leaks happen.
  • How to structure your code to avoid them.
  • A design pattern to manage dynamic memory safely (RAII without smart pointers).
  • A reusable ScopedPointer class to emulate unique_ptr in old C++.

The Problem: Memory Leaks from Raw Pointers

Consider this code:

void loadData() {
    char* buffer = new char[1024];
    // some processing...
    if (someCondition()) {
        return; // leak!
    }
    delete[] buffer;
}

What’s wrong? If someCondition() returns true, buffer is never deallocated.


Solution 1: Manual try/catch + delete (not scalable)

void loadData() {
    char* buffer = new char[1024];
    try {
        if (someCondition()) {
            throw std::runtime_error("Something went wrong");
        }
        // more code...
    } catch (...) {
        delete[] buffer;
        throw;
    }
    delete[] buffer;
}

Not elegant. Easy to forget or misplace deletes. Let's go better.


Solution 2: Use RAII Even Without Smart Pointers

RAII (Resource Acquisition Is Initialization) is a pattern where resource allocation is tied to object lifetime. When an object goes out of scope, its destructor cleans up.

Let’s build a small ScopedPointer class.


ScopedPointer.h

template <typename T>
class ScopedPointer {
private:
    T* ptr;

public:
    explicit ScopedPointer(T* p = nullptr) : ptr(p) {}

    ~ScopedPointer() {
        delete ptr;
    }

    T& operator*() const { return *ptr; }
    T* operator->() const { return ptr; }
    T* get() const { return ptr; }

    void reset(T* p = nullptr) {
        if (ptr != p) {
            delete ptr;
            ptr = p;
        }
    }

    // Prevent copy
    ScopedPointer(const ScopedPointer&) = delete;
    ScopedPointer& operator=(const ScopedPointer&) = delete;
};

For arrays:

template <typename T>
class ScopedArray {
private:
    T* ptr;

public:
    explicit ScopedArray(T* p = nullptr) : ptr(p) {}

    ~ScopedArray() {
        delete[] ptr;
    }

    T& operator[](int index) const { return ptr[index]; }
    T* get() const { return ptr; }

    void reset(T* p = nullptr) {
        if (ptr != p) {
            delete[] ptr;
            ptr = p;
        }
    }

    // Prevent copy
    ScopedArray(const ScopedArray&) = delete;
    ScopedArray& operator=(const ScopedArray&) = delete;
};

Usage

#include "ScopedPointer.h"

void loadData() {
    ScopedArray<char> buffer(new char[1024]);

    if (someCondition()) {
        return; // no memory leak!
    }

    // buffer is auto-deleted when going out of scope
}

Bonus: Integrating with Legacy C APIs

Some legacy APIs require raw pointers. You can still use get():

void legacyFunction(char* data);

void useLegacyAPI() {
    ScopedArray<char> buffer(new char[512]);
    legacyFunction(buffer.get());
}

Conclusion

Even without smart pointers, you can manage memory safely in C++ using the RAII pattern. This approach:

  • Prevents memory leaks.
  • Simplifies exception handling.
  • Keeps your code clean and maintainable.

In newer projects, always prefer std::unique_ptr and std::shared_ptr. But in legacy systems, RAII with simple wrappers like ScopedPointer can save you.

Continue Reading

Handpicked posts just for you — based on your current read.

Discussion 0

Please sign in to join the discussion.

No comments yet. Start the discussion!