Page 1 of 1

[C++0x]Memory management and Smart pointers Rate Topic: ***** 1 Votes

#1 Karel-Lodewijk  Icon User is offline

  • D.I.C Addict
  • member icon

Reputation: 451
  • View blog
  • Posts: 855
  • Joined: 17-March 11

Posted 01 September 2011 - 10:35 AM

*
POPULAR

Memory management and Smart pointers

All examples will assume, you are using the newly approved but not yet officially published C++0x standard, if you can't or won't use this, all classes/constructs here are implemented in the excellent boost library as well. All examples compile with gcc 4.5.2 and the compile flags "g++ -std=c++0x", please post a comment if they do now work on your platform so I can make a note of it. Not all functions of the C++0x are implemented on all platforms.


The memory leak


Let us start by explaining the problem. Whenever you allocate memory on the heap, regardless of the fact you do it with new, new[], or the c memory allocation functions like malloc, you are responsible for cleaning it up by calling the appropriate delete, delete[] or free. If you do not or you do it wrong, you create what is known as a memory leak. This means that memory was allocated that is no longer accessible because you have no pointer any more that refers to it, but it also not freed, so it didn't return to the available pool of memory. If your program runs for a long time and frequently leaks memory it will in fact use up all available memory in your system, which is bad.


Difficulties in memory management

Wise people once said that you should match every call that allocates memory with the appropriate call that deallocated the memory. new/delete, new[]/delete[], malloc/free. But even then you have to make certain that the statement which deallocates the memory is actually reached.
A common mistake for example which can even catch experienced programmers off guard is when the program throws an error, you catch the error and recover, but by skipping all the code after you throw the error, you miss the statement that would have cleaned up the memory.
Another problem could be that it is not exactly clear where the memory should be freed. It is quite common that a pointer is passed around or copied to more than once place,


Concept of ownership

Smart programmers identified that all variables and memory should have an owner. Someone or something that is responsible for controling the lifetime for a variable or a piece of memory. For local variables and parameters this is the scope they belong to, because if the scope ends so do the parameters and variable. For class members it is the object of the class. If the object dies so do the member variables. For global variables and static variables, this is the program. If the program dies so do the global and static variables.
For memory it is a little bit more difficult, because pointers to memory can be passed around. But if a programmer keeps in mind which classe(s) or scope owns a piece of memory at any given time. Then it becomes automatically clear where and when the memory should be released.


Ownership models

For the purpose of smart pointers we will make the distinction between 3 ownership models.

  • The memory is owned by the scope: This is just like regular variables, when the scope ends, the memory should be released. If this is the case you probably should have declared a local variable instead of allocating it with new, so it is actually not used as much.
  • The memory is passed around but only one class or scope can claim ownership at any given time: So the moment the pointer is passed to somewhere else, you will never use it again.
  • The memory is shared: This is the most flexible ownership model and in fact the default for any language with garbage collection like java. This is saying there is no clear owner, but the memory can be accessed at different places.


To enforce this ownership so the memory will actually be released automatically is the reason smart pointers were invented.


Smart pointers

But first, what is a smart pointer: A smart pointer is an object that contains a pointer and behaves like a pointer, but will automatically release it's memory when certain conditions are met. You create one by passing it a pointer and after that you let the smart pointer take care of delete/delete[]/free, in fact it is an error to try to free the memory manually. For every other purpose but delete/delete[]/free, the pointer behaves just like a regular pointer. It is dereferences with *, you can use the operator -> to call a functions. In addition you can get the underlying pointer with the get() function, but do not call delete on it.

There are different kinds of smart pointers, to enforce the different ownership models, they all reside in the memory header (#include <memory>). Here I will give a quick explanation. Bellow I will go in more depth about each one.

  • const std::unique_ptr: When a pointer is managed by a const std::unique_ptr, the memory refered to will be freed when the scope in which a const std::unique_ptr exists ends. (This is almost equivalent to the behavior of a boost::scoped_ptr)
  • std::unique_ptr: When a pointer is managed by a unique pointer, the memory will be freed when the owning class or scope dies/ends unless the pointer/memory is MOVED to another std::unique_ptr. It is also no longer allowed to use the unique_ptr after it/it's memory has been moved.
  • std::shared_ptr: When a pointer is managed by a shared pointer, the memory/pointer can be COPIED to other std::shared_ptr. When no more copies of a particular std::shared_ptr exists, the memory will be freed. This is almost (read pitfalls to understand the almost) has the same effect as automatic garbage collection like in languages such as java and most scripting languages.



const std::scoped_ptr or scoped pointer

So let us look at a simple example piece of code:

#include <iostream>
#include <memory>

class A {
  public:
   void f() {
        std::cout << "A::f()" << std::endl;
    }
};

int main() {
    {
        const std::unique_ptr<int> an_int_pointer(new int(5));
        *an_int_pointer = 10; // I can be used like a pointer
        std::cout << *an_int_pointer << std::endl;
    } //the memory to the integer is released
    
    {
        const std::unique_ptr<A> an_A_pointer(new A());
        an_A_pointer->f(); // I can be used like a pointer
    } //the memory for the A object is released   
}



This should be pretty self explanatory. Notice that the unique_pointer does need a type as template parameter. Also notice that I call new when creating the smart pointer, you do not have to do this, but it is actually recommended. By never explicitly creating the actual pointer, I certainly will not be tempted to use it. Mixing regular pointers and smart pointers referring to the same is a recipe for disaster.


std::unique_ptr

This time the example is a bit trickier. Even to experienced c++ programmers, this can look a little bit alien as unique_ptr has close ties with a new language feature rvalue references and move semantics. There is an excellent introduction here http://www.open-std....2006/n2027.html, but for the purpose of this smart pointer tutorial I will give a brief explanation of what we actually use here.

#include <memory>

class A {  
  public:   
    //move the integer in but do not automatically claim ownership, ownership must be released by the caller
    void move_int_in(std::unique_ptr<int>&& an_int_) {
        an_int = std::move(an_int_);
    }
    
    //move the integer out, but doesn't automatically release ownership
    std::unique_ptr<int>& move_int_out() {
        return an_int;
    }

    std::unique_ptr<int> an_int;
};

int main() {
    std::unique_ptr<int> the_int(new int(5));
    std::unique_ptr<int> the_same_int = std::move(the_int);
    
    //don't use the_int after this point, it does no longer has ownership
    
    A a;        
    //With these two function the caller must explicitly release
    //and take ownership with std::move
    a.move_int_in(std::move(the_int)); //the_same_int releases ownership
    the_same_int = std::move(a.move_int_out()); //the_same_int takes ownership

} //the_same_int currently had ownership and died because it went out of scope, the memory is released



Let's start with the lines

std::unique_ptr<int> the_int(new int(5));
std::unique_ptr<int> the_same_int = std::move(the_int);



This is how ownership of a pointer is moved. Notice that the std::move is necessary, it means, release ownership from the smart pointer on the right and give it to the smart pointer to the right. After this call, you should no longer use the_int to refer to the integer, doing so will probably result in a segmentation fault.

The class example uses a new feature of c++0x, move semantics. We know about passing values by value, by pointer, by reference, but now there is a 4th way in c++, by move semantics. Basically this means that you can now move values to a function without copying them or creating a reference to an existing object, causing no overhead. But a condition is that the value to the right will never be used again. This behavior is invoked when a parameter or a value to the right of an assignment is a temporary object or when you explicitly state that it is safe to move the object by using std::move. Passing values by move semantics is indicated by the new operator &&.

Now the function move_int_in should make sense. move_int_in takes a parameter of type std::unique_ptr<int>&&, so a std::unique_ptr<int>, but only if it safe to move.

If you don't want the std::move in your calling code, then this can be achieved as well, but it is not recommended. Remember the age old mantra explicit is better than implicit.

#include <memory>

class A {
  public:
    //move the integer in from a smart pointer and automatically claim ownership
    void move_int_in_and_take_ownership(std::unique_ptr<int>& an_int_) {
        an_int = std::move(an_int_);
    }

    //move the integer out and automatically release ownership even when no-one is there to take ownership
    std::unique_ptr<int> move_int_out_and_release_ownership() {
        return std::move(an_int);
    }
    
    std::unique_ptr<int> an_int;
};
    
int main() {
    std::unique_ptr<int> the_int(new int(5));
    std::unique_ptr<int> the_same_int = std::move(the_int);
    
    //don't use the_int after this point, it does no longer has ownership
    
    A a;       
    //With these two functions the class automatically takes 
    //ownership of the memory/pointer, see also the function 
    //definitions within the class
    a.move_int_in_and_take_ownership(the_same_int); //the_same_int releases ownership, see class
    the_same_int = a.move_int_out_and_release_ownership(); //the_same_int takes ownership, see class
}



Note that the stl containers in c++0x now also work with move semantics. It is perfectly valid and safe to create an stl container that contains unique_ptr or in fact any other type of smart pointer and if an element of the stl container is erased, the memory will be freed through the dynamics of the chosen smart pointer. So you can do something like this.

std::vector<std::unique_ptr<int> > int_pointers;
int_pointers.push_back(new int(1));
int_pointers.push_back(new int(2));
int_pointers.push_back(new int(3));
int_pointers.clear(); //all memory is freed.



You can also move pointers from a unique_ptr into an stl container like this.

std::vector<std::unique_ptr<int> > int_pointers;
std::unique_ptr<int> an_int(new int(5));
int_pointers.push_back(std::move(an_int));
int_pointers.clear(); //all memory is freed.



Remember that when extracting elements, you have to explicitly move them out.

std::unique_ptr<int> last_int = std::move(int_pointers.back());
int_pointers.pop_back();



std::shared_ptr

#include <iostream>
#include <memory>

class A {  
  public:
    std::shared_ptr<int> an_int;
};

class B {  
  public:
    std::shared_ptr<int> an_int;
};

int main() {
    std::shared_ptr<int> the_int(new int(5));
    std::shared_ptr<int> the_same_int = the_int;
    std::shared_ptr<int> still_the_same_int(the_int);
    
    A a;
    a.an_int = the_int;
    
    B b;
    b.an_int = the_same_int;

} //the_int, the_same_int, still_the_same_int, a.an_int, b.an_int are destroyed and thus the memory to the int is released



The code should be pretty self explanatory, you can have a piece of memory shared all over the place and yet proper cleanup will happen. The programmer needs not to be aware who still has access to the memory. When the last shared pointer pointing to the memory dies, so does the memory.

Now under some circumstances you want a copy of a shared pointer that does not prevent its destruction. For example you want to be able to do something with an object, but only if it still exists, also read pitfalls for other situations this could be useful. For this purpose there exists the std::weak_ptr.

A weak_ptr in itself doesn't do very much, it doesn't for example let you manipulate it like a pointer. You can however call expired() on it, to let you know if the object it points to still exists and you can create a shared_ptr out of it, that you can manipulate if it does.

#include <iostream>
#include <memory>

int main() {
    std::weak_ptr<int> weak_pointer;
    {
        std::shared_ptr<int> a(new int(5));
        weak_pointer = a;       
        if (!weak_pointer.expired()) {
            std::shared_ptr<int> temp_ptr(weak_pointer); //you must create a shared_ptr with it to manipulate it do anything with it
            std::cout << *temp_ptr << std::endl;
        }
    }
    if (!weak_pointer.expired()) {
        //we won't get here, the object weak_pointer pointed to has expired
    }  
}



You can also optionally use move semantics with shared pointers.

std::shared_ptr<int> the_int(new int(5));
std::shared_ptr<int> the_same_int = std::move(the_int);



Note that it is now unsafe to use the_int as it now no longer owns the pointer to the int. std::move also causes slightly less overhead.

Shared pointers can also safely be stored within stl containers. In fact they will actually use the std::move internally when appropriate, so they cause less overhead.


So it works with new, but does it also work with new[] and malloc ?

Yes, but not automatically. A smart pointer actually takes 2 parameters, a pointer and a deleter. It is upto the user to properly match the deleter, where plain delete is the default. If you allocate memory with new[] or malloc, you should create your smart pointers as follows:

std::unique_ptr<int[]> a(new int[5]);



This gives access to the operator [] on a, so you can work with it like an array, and will properly call delete[]

For memory allocated with the malloc family of allocation functions, you can use:

std::unique_ptr<int, decltype(&std::free)>((int*)malloc(5*sizeof(int)), std::free); 



Notice how we use decltype, another new feature of c++0x. it is the same as:

std::unique_ptr<int, void (*)(void*)>((int*)malloc(5*sizeof(int)), std::free);



You can do even fancier things with the custom deleter, for example a deleter that also closes a file descriptor comes to mind.

Ofcourse the same will work with shared_ptr.


Note on performance

The performance overhead of smart pointers is really quite small. In fact for a unique_ptr no extra memory is used and moving one is the same amount of work as copying one address and assigning zero to the original pointer, which is recommended you do with regular pointers anyway. So unique_ptr, properly implemented has no overhead. shared_ptr has a little overhead, it maintains a count of all smart pointers sharing a pointer. So every time a shared_ptr is created or destroyed a reference count is incremented or decremented and when it is decremented it is checked.


Pitfalls

Unfortunately smart pointers can not always be relied upon to free memory. Consider the following example.

#include <iostream>
#include <memory>
#include "cstdio"

class B;

class A {
  public:
    std::shared_ptr<B> a_B_pointer;
};

class B {
  public:
    std::shared_ptr<A> an_A_pointer;
};

int main() {
    {
        std::shared_ptr<A> a(new A());
        std::shared_ptr<B> b(new B());
        a->a_B_pointer = b;
        b->an_A_pointer = a;
    }
    
    //the memory allocated for a and b is not freed and is also no longer accesible. 
    //It is not freed because a points to b with a shared_ptr and b points
    //to a with a shared_ptr, so they keep eachother alive.
}



The solution would be to make one of the two shared_ptr a str::weak_ptr

This and only this is the advantage automatic garbage collection has over smart pointers. It will detect these kinds of cycles but usually at much greater computational cost to the garbage collector.


FAQ

Is there anyway to release my pointers from the grasp of smart pointers ?

You can release your pointer from a unique_ptr, this is safe and is not in breach of the contract of the unique_ptr. Just call release() on a unique_ptr. This is the same as moving a pointer out of a smart pointer. You cannot do this with a shared_ptr. So once you wrap a pointer into a shared pointer, you have no way to unwrap it again.

Can I copy a unique_ptr ?

No you can not. It goes against the contract of a unique_ptr, making a copy wouldn't make it unique.

can I really not make a copy ?

Well, not really, as always you can work your way around it, but it isn't pretty, and using anything but the last created copy is unsafe. Basically what you end up with is the behavior of auto_ptr, which was deprecated for a reason.

#ifndef COPYABLE_UNIQUE_PTR_H
#define COPYABLE_UNIQUE_PTR_H

#include <memory>

/** Usage of this class is only safe if you can guarantee that only the most recent copy
 *  of the smart pointer will be used. It uses move on copy semantics like the deprecated
 *  auto_ptr.
 */
template <class T, class Del = std::default_delete<T> >
class copyable_unique_ptr : public std::unique_ptr<T, Del> {
  public:
    //A fully C++0x compliant c++ compiler should accept the following line to inherit constructors from unique_ptr.
    //gcc as of 4.5.2 with -std=c++0x, however does not. So we manually write wrappers for the constructors we need
    //using std::unique_ptr<T, Del>::std::unique_ptr<T, Del>;
  
    copyable_unique_ptr(T* pointer, Del deleter = Del()) : std::unique_ptr<T, Del>(pointer, deleter) {}
    copyable_unique_ptr(std::unique_ptr<T,Del>& pointer) : std::unique_ptr<T, Del>(std::move(pointer)) {}
    
    //copy constructors which moves the pointer from the old unique_ptr to the new one when copied.
    copyable_unique_ptr(const copyable_unique_ptr<T,Del>& other) : std::unique_ptr<T, Del>(std::move(const_cast<copyable_unique_ptr<T,Del>& >(other))) {}
};
#endif // COPYABLE_UNIQUE_PTR_H



I've used it to be able to use a unique_ptr with boost::bind, when I knew the boost bind function object would only be called once. But it is not recommended the const_cast is also a sign for the compiler to abandon many optimizations.

Is This A Good Question/Topic? 5
  • +

Replies To: [C++0x]Memory management and Smart pointers

#2 OLH064  Icon User is offline

  • Junior bit compressor

Reputation: 20
  • View blog
  • Posts: 725
  • Joined: 06-June 11

Posted 12 December 2011 - 11:03 AM

C++0x? Do you have a link to a page for download?
Was This Post Helpful? 0
  • +
  • -

#3 Karel-Lodewijk  Icon User is offline

  • D.I.C Addict
  • member icon

Reputation: 451
  • View blog
  • Posts: 855
  • Joined: 17-March 11

Posted 14 December 2011 - 06:00 PM

View PostOLH064, on 12 December 2011 - 11:03 AM, said:

C++0x? Do you have a link to a page for download?


C++0x was the working name of the c++11 standard. All active compilers are in the process of building in support for it.

If no compiler with c++11 support is available, boost has smart pointers with almost the same syntax, so most of it should still apply.

This post has been edited by Karel-Lodewijk: 14 December 2011 - 06:00 PM

Was This Post Helpful? 0
  • +
  • -

#4 Aphex19  Icon User is offline

  • Born again Pastafarian.
  • member icon

Reputation: 615
  • View blog
  • Posts: 1,873
  • Joined: 02-August 09

Posted 21 December 2011 - 08:01 AM

Very useful, in-depth tutorial, thanks alot Karel-Lodewijk.
Was This Post Helpful? 0
  • +
  • -

#5 Seefer  Icon User is offline

  • New D.I.C Head

Reputation: 0
  • View blog
  • Posts: 1
  • Joined: 31-March 12

Posted 31 March 2012 - 10:14 AM

Excellent coverage of smart pointers. Clarified a lot of things for me compared to other discussions I've read elsewhere in Internet land. Thanks for taking the time to do this Karel-Lodewijk.
Was This Post Helpful? 0
  • +
  • -

Page 1 of 1