Sobes.tech
Middle+

How can memory leaks occur when using std::shared_ptr?

sobes.tech AI

Answer from AI

Memory leaks when using std::shared_ptr are most often caused by cyclic references. When two or more std::shared_ptr objects refer to each other, their reference counts can remain non-zero even if there are no external references to these objects. This prevents the objects from being destroyed and the associated memory from being freed.

Example of a cyclic reference:

struct Foo {
    std::shared_ptr<Foo> other;
};

auto p1 = std::make_shared<Foo>();
auto p2 = std::make_shared<Foo>();

p1->other = p2; // p1 refers to p2
p2->other = p1; // p2 refers to p1

// After p1 and p2 go out of scope, their reference counts
// will remain at 1 due to mutual references, preventing deallocation.

To prevent such leaks, std::weak_ptr is used. std::weak_ptr holds a non-owning reference to an object managed by std::shared_ptr. It does not increase the reference count.

Example of preventing cyclic references with std::weak_ptr:

struct Foo {
    std::weak_ptr<Foo> other; // Using weak_ptr
};

auto p1 = std::make_shared<Foo>();
auto p2 = std::make_shared<Foo>();

p1->other = p2;
p2->other = p1;

// When p1 and p2 go out of scope, their shared_ptrs are destroyed,
// reference counts become zero, and memory is freed.
// weak_ptr does not prevent this.

Another, less common way to cause memory leaks with std::shared_ptr is by using custom deleters that contain errors or do not perform their task properly.

// Faulty deleter that does nothing
void faulty_deleter(int* ptr) {
    // delete ptr is not called
}

auto p = std::shared_ptr<int>(new int(10), faulty_deleter);
// Memory allocated for int will not be freed when p is destroyed.

Using exceptions during the creation of std::shared_ptr can lead to loss of ownership over raw pointers if they were created before initializing the shared_ptr. It is recommended to use std::make_shared or std::allocate_shared for atomic allocation.

int* raw_ptr = new int(5);
// If an exception occurs here before creating the shared_ptr
// raw_ptr memory will be lost
// std::shared_ptr<int> sp(raw_ptr); // Creating shared_ptr