DeveloperBreeze

Avoiding Memory Leaks in C++ Without Smart Pointers

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.

Related Posts

More content you might like

Tutorial

Deep Copy in C++: How to Avoid Shallow Copy Pitfalls

A shallow copy copies the values of member variables as-is. If your class has a pointer member, both the original and copy point to the same memory.

class Shallow {
public:
    int* data;

    Shallow(int val) {
        data = new int(val);
    }

    ~Shallow() {
        delete data;
    }
};

Apr 11, 2025
Read More
Tutorial

Implementing a Domain-Specific Language (DSL) with LLVM and C++

#ifndef DSL_AST_H
#define DSL_AST_H

#include <memory>
#include <llvm/IR/Value.h>
#include <llvm/IR/IRBuilder.h>

// Base class for all expression nodes.
class ASTNode {
public:
    virtual ~ASTNode() = default;
    virtual llvm::Value* codegen(llvm::IRBuilder<>& builder) = 0;
};

// Expression for numeric literals.
class NumberExprAST : public ASTNode {
public:
    NumberExprAST(double value) : value(value) {}
    llvm::Value* codegen(llvm::IRBuilder<>& builder) override;

private:
    double value;
};

// Expression for a binary operator.
class BinaryExprAST : public ASTNode {
public:
    BinaryExprAST(llvm::TokenType op, std::unique_ptr<ASTNode> lhs,
                  std::unique_ptr<ASTNode> rhs)
        : op(op), lhs(std::move(lhs)), rhs(std::move(rhs)) {}
    llvm::Value* codegen(llvm::IRBuilder<>& builder) override;

private:
    TokenType op;
    std::unique_ptr<ASTNode> lhs, rhs;
};

#endif // DSL_AST_H

Implementation: AST.cpp

Feb 12, 2025
Read More
Tutorial
csharp

Developing a Real-Time Multiplayer Game with Unity and C#

  • In the Hierarchy window, right-click and create a new 3D Object > Plane to serve as the ground.
  • Create a 3D Object > Cube to act as the player character.
  • Position the cube above the plane so that it is ready to fall due to gravity.
  • Add a Camera and Light to the scene if they are not already present.
  • Right-click in the Hierarchy and create an empty GameObject. Name it "NetworkManager."
  • Add the NetworkManager component by clicking Add Component and searching for "NetworkManager."
  • Also, add the NetworkManagerHUD component to provide basic controls for starting and stopping the server or client.

Aug 14, 2024
Read More
Code
csharp

Unity Inventory System using Scriptable Objects

No preview available for this content.

Aug 12, 2024
Read More

Discussion 0

Please sign in to join the discussion.

No comments yet. Be the first to share your thoughts!