Skip to content

Instantly share code, notes, and snippets.

@mikadou
Created May 9, 2016 06:24
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mikadou/1578da33c2c36fdb308af54200764a00 to your computer and use it in GitHub Desktop.
Save mikadou/1578da33c2c36fdb308af54200764a00 to your computer and use it in GitHub Desktop.

Prevention of memory leaks

In C++ the programmer is responsible for correctly allocating and releasing memory resources. Logical errors in the program can cause memory leaks, i.e. previously allocated memory is not being properly released after usage. The following guideline shows how to minimize the risk of accidentically leaking memory.

Do not dynamically allocate memory without reason

The obviously easiest way to avoid memory leaks, is to not dynamically allocate memory at all (i.e. using new or even malloc). More often than it should be, objects are dynamically allocated with new. This habit probably was carried over by Java programmers into the world of C++. Most of the time, however, in C++ it is sufficient to directly create objects on the stack:

MyClass obj(arg1, arg2);  // do this instead of MyClass *obj = new MyClass(arg1, arg2);

// This also applies to class members
class Foo {
public:
    Foo()
    : m_data(MyClass(arg1,arg2))  // instead of m_data(new MyClass(arg1, arg2))
    {}
private:
    MyClass m_data;  // instead of MyClass *m_data
};

In the example above, the object is destroyed as soon as it goes out of scope. Accordingly, objects which are members of an enclosing object are also properly destructed as soon as the lifetime of the enclosing object ends. No manual delete is required to release allocated memory and subsequently no accidental leakage of memory can occur.

Some circumstances where dynamic allocation of memory might still be justified are:

  • Implementing polymorphic behaviour (I.e. using a pointer to a base class with specialized derived classes).
  • Objects which are either owned by multiple objects or may change their owner during their lifetime.
  • Allocating very large memory chunks (in order too avoid stack overflow).

Still, this doesn't mean one should forget about all good coding practices. Useful guidelines for clean implementations are discussed below.

Use smart pointers instead of raw pointers

Since C++11 smart pointers are available in the standard in form of std::unique_ptr and std::shared_ptr. The advantage of these types over raw pointers is that they convey clear ownership semantics. In the context of pointers there often is the question: Who is responsible for deleting the object a pointer is pointing to? What if there are other pointers still pointing to and using this object? Unclear ownership facilicates the introduction of memory leaks into the code. Smart pointers solve this problem by owning the dynamically allocated object they are pointing to and automatically deleting it as soon as it is no longer used.

For std::unique_ptr ownership is very straightforward, because only one single unique_ptr can point to a specific object at a time. At the time the unique_ptr goes out of scope it destructs it's associated object and releases the memory.

On the other hand, multiple std::shared_ptr can point to the same object, which implies shared ownership. In this case the associated object is only destructed and released after the lifetimes of all shared_ptrs pointing to it ceased to exist. This is implemented via internal reference counting.

By employing smart pointers, delete no longer needs to be called manually. Proper destruction of objects and release of memory resources is guaranteed for all possible codepaths without extra effort. This advantage comes at the expense of a slight runtime overhead. When using raw pointers over smart pointers for reasons of performance, please communicate this decision clearly and explain why it was necessary.

Use RAII for dynamically allocated memory

If it really is required to manually allocate and release memory in a dynamic fashion and refer to it with raw pointers, then at least avoid doing so within the general control flow of the program. Again, the problem is that an according delete will have to be called on every possible code path. As the code grows, with new code paths being added, there is a high possibility of introducing memory leaks later on. Even if the code is correct, it unnecessarily complicates the reasoning about the correctness of the implementation.

Instead, prefer to manage the memory resource within a dedicated class following the RAII idiom. Allocate memory on the heap during construction and release it accordingly in the destructor. An instance of this class can now be utilized directly on the stack and all memory cleanup is taken care of automatically:

class MyMemory {
public:
  MyMemory() {
    // allocate memory
  }
  ~MyMemory() {
    // release memory
  }
private:
  // internal pointer to memory
};

void func(){
    MyMemory mem;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment