Avoid memory leak using shared_ptr

Shared_ptr was first designed and implemented in boost, and finally became part of the C++11 Standard as std::shared_ptr. Boost defines it as, “The shared_ptr class template stores a pointer to a dynamically allocated object, typically with a C++ new-expression. The object pointed to is guaranteed to be deleted when the last shared_ptr pointing to it is destroyed or reset.” This auto-delete character makes it possible to avoid memory leak.

It’s not hard to use shared_ptr. We just create an object, pass it to the shared_ptr, use the pointer as a common one, and have no need to care about when to delete it. Here is a simple example:

#include <iostream>
#include <memory>

class UserDefinedClass{
  public:
    UserDefinedClass();
    int     GetValue();
    void    SetValue(const int value);

  private:
    int value_;
};

UserDefinedClass::UserDefinedClass() {
    value_ = 0;
}

int UserDefinedClass::GetValue() {
    return value_;
}

void UserDefinedClass::SetValue(const int value) {
    value_  =    value;
}

int main() {
    std::shared_ptr<UserDefinedClass> sp1;
    std::shared_ptr<UserDefinedClass> sp2(new UserDefinedClass);

    sp1 = sp2;
    sp1->SetValue(10);
    sp2->SetValue(20);

     if (sp1) std::cout << "sp1: " << sp1->GetValue() << '\n';
     if (sp2) std::cout << "sp2: " << sp2->GetValue() << '\n';

     return 0;
}

In the above code, we can eliminate the “new” operation by using “make_shared” to get an efficiency benefit by consolidating allocation.

When considering arrays, we can combined the vector with shared_ptr. The following example will show us how to do.

#include <cstdio>
#include <cstring>
#include <memory>
#include <thread>
#include <mutex>
#include <vector>
#include <semaphore.h>

using namespace std;

#define   MAX_CACHE_SIZE    100
#define   MAX_BUFFER_SIZE   (1024*1024)

struct MemoryUnit{
    int size;
    // there is a space between the two '>' characters
    shared_ptr<vector<char> > buffer;
};

struct MemoryCache{
    pthread_mutex_t  mutex_cache;
    sem_t    sem_empty;
    sem_t    sem_full;
    vector<struct MemoryUnit> cached_vector;
};

class SharedPtrDemo{
  public:
    SharedPtrDemo();
    ~SharedPtrDemo();
    void Start();

  private:
    struct MemoryCache  memory_cache;
    pthread_t  producer_thread_id;
    pthread_t  consumer_thread_id;
    void  Producer();
    void  Consumer();

    static void* ProducerEntrance(void* args)  {
        if (NULL == args) return NULL;

        SharedPtrDemo* instance = static_cast<SharedPtrDemo*>(args);
        if (instance)
            instance->Producer();

        return NULL;
    }

    static void* ConsumerEntrance(void* args) {
        if (NULL == args) return NULL;

        SharedPtrDemo* instance = static_cast<SharedPtrDemo*>(args);
        if (instance)
            instance->Consumer();

        return NULL;
    }
};

SharedPtrDemo::SharedPtrDemo() {
    pthread_mutex_init(&memory_cache.mutex_cache, NULL);
    sem_init(&memory_cache.sem_empty, 0, MAX_CACHE_SIZE);
    sem_init(&memory_cache.sem_full, 0, 0);
}

SharedPtrDemo::~SharedPtrDemo() {
    pthread_mutex_destroy(&memory_cache.mutex_cache);
    sem_destroy(&memory_cache.sem_empty);
    sem_destroy(&memory_cache.sem_full);
}

void SharedPtrDemo::Start() {
    pthread_create(&producer_thread_id,
                   NULL,
                   SharedPtrDemo::ProducerEntrance,
                   (SharedPtrDemo* )this);

    pthread_create(&consumer_thread_id,
                   NULL,
                   SharedPtrDemo::ConsumerEntrance,
                   (SharedPtrDemo* )this);
}

void SharedPtrDemo::Producer() {
    unsigned int  produced_count = 0;

    while(1) {
        ++produced_count;
        struct MemoryUnit produced_unit;
        memset(&produced_unit, 0x00, sizeof(produced_unit));

        srand(time(0)+produced_count);
        int memory_size = rand() % MAX_BUFFER_SIZE + 1;
        produced_unit.buffer = make_shared<vector<char> >(memory_size);

        // put whatever you want to the buffer
        memcpy(&(*produced_unit.buffer)[0], &produced_count,sizeof(produced_count));
        produced_unit.size =  memory_size;

        sem_wait(&memory_cache.sem_empty);
        pthread_mutex_lock(&memory_cache.mutex_cache);
        memory_cache.cached_vector.push_back(produced_unit);
        pthread_mutex_unlock(&memory_cache.mutex_cache);
        sem_post(&memory_cache.sem_full);
    }
}

void SharedPtrDemo:: Consumer() {
    int passed_value = 0;
    unsigned int consumed_count = 0;

    while(1) {
        struct MemoryUnit consumed_unit;
        memset(&consumed_unit, 0x00, sizeof(consumed_unit));

        sem_wait(&memory_cache.sem_full);
        pthread_mutex_lock(&memory_cache.mutex_cache);
        vector<struct MemoryUnit>::iterator vector_it
            = memory_cache.cached_vector.begin();
        consumed_unit = *vector_it;
        memory_cache.cached_vector.erase(vector_it);
        pthread_mutex_unlock(&memory_cache.mutex_cache);
        sem_post(&memory_cache.sem_empty);

        // use the buffer, then it will be destroyed automatically
        memcpy(&passed_value, &(*consumed_unit.buffer)[0], sizeof(passed_value));
        printf("Consumed %d units, and the value = %d\n",
               ++consumed_count, passed_value);
    }
}

int main() {
    SharedPtrDemo demo;
    demo.Start();

    getchar();

    return 0;
}

Convenient as it is, there are some potential dangers when using shared_ptr.

1)Avoid the circular references

Because the implementation of shared_ptr uses reference counting, while circular references will make the mechanism fail. Here is an example:

#include <memory>
#include <iostream>

using namespace std;

class B;
class A{
  public:
    shared_ptr<B> b_ptr;
    A() {cout << "A() \n";}
    ~A() {cout << "~A() \n";}
};

class B{
  public:
    shared_ptr<A> a_ptr;  // use weak_ptr to break the cycle
    B() {cout << "B() \n";}
    ~B() {cout << "~B() \n";}
};

int main() {
    shared_ptr<A> a_ = make_shared<A>();
    shared_ptr<B> b_ = make_shared<B>();
    a_->b_ptr = b_;
    b_->a_ptr = a_;

    return 0;
}

This program will end up with print “A()” and “B()”. There’s no destruction of A and B. There will surely lead to memory leak, and make a long-lived program be killed eventually. To break the cycle, we can use weak_ptr.

2)Always use make_shared instead of new

Make_shared will help us to achieve high performance and avoid memory leak in some situation. For example, code like F(std::shared_ptr<T>(new T), g()) might cause a memory leak if g throws an exception because g() may be called after new T and before the constructor of shared_ptr<T>. While this doesn’t occur in F(std::make_shared<T>(), g()), since two function calls are never interleaved.

3) Avoid to use the raw pointer

Calling the get() function to get the raw pointer or passing a reference of a raw pointer to a shared_ptr would be dangerous, because the internal count won’t increase.

As we’ve learned the memory fragmentation problem before. Will there be the same problem with shared_ptr? Well, we can see that shared_ptr uses new to allocate memory on the heap. And some memory usage patterns, those with many long-lived small objects, are trend to cause memory fragmentation. And we can use third party memory libraries such as jemalloc and tcmalloc to avoid it.

Reference

1)Official definition of shared_ptr class template

http://www.boost.org/doc/libs/1_61_0/libs/smart_ptr/shared_ptr.htm

2)Potential dangers when using boost::shared_ptr

http://stackoverflow.com/questions/701456/what-are-potential-dangers-when-using-boostshared-ptr

3)Why should we almost always use make_shared

GotW #89 Solution: Smart Pointers

4)Performance compared with make_shared and new

http://tech-foo.blogspot.jp/2012/04/experimenting-with-c-stdmakeshared.html

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s