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.