Memory in C++
One of the major parts of C++11 is the new smart pointers. But why do we need them, and why should you steer away from the old style pointers?
This post is intended both as an introduction, and a reference guide to the smart pointers found in
C++11. It will cover what they are, how to use them and how they work.Memory allocation in C++
C++ supports
dynamic allocation ( new / delete ). It's a way of creating a object whenever you need it. The object will live until you call delete on it. Otherwise it won't be deleted, not even when it goes out of scope. And if you don't delete it, none will, which in turns causes memory leaks. It might also cause undefined behavior ( which means anything can happen ) if, for instance, you delete something and try to use it afterwards.Smart pointer will help you with these issues and make your code cleaner and better.
Smart pointers
Smart pointers are like regular pointers, but they provide extra functionality such as automatic checks or other safety mechanisms. C++11 introduces three smart pointer types :
- unique_ptr
- Only one
unique_ptrcan point to the object at the time - shared_ptr
- Several
share_ptrs can point to the object at a time. - Object is released when no
share_ptrs point to the object - weak_ptr
- Non-owning, needs to be converted into an
shared_ptrto use the object it points to
Using smart pointers
Once you have created the smart pointer, you can use it as a regular pointer. So operations like
!=, !, !, ->, * etc. This is not true for weak_ptr, but we'll get to that later. You can also use them in
ifs like this : if ( ptr ) this will return true as long as the smart pointer is initialized and is managing a valid object ( meaning you can use the pointer. )We'll now take an in-depth look at the smart pointers, one at the time.
unique_ptr
An
unique_ptr is the sole owner of an object that it points to. Two unique_ptrs can also swap objects, but they can never manage the same object. And as with all smart pointers, it takes care of deleting the object. Initialization and allocation
There are two ways of doing this, regular constructor or
make_sharedconstructor
The constructor is pretty straight forwards. You just set the template arguments ( in this case
Foo ) and pass an allocated ( new'ed ) object :std::unique_ptr< Foo> p1(new Foo);This is pretty straight forwards ; template parameter is the type and you supply an allocated (
new'ed ) object. make_unique
make_uniqueThe second alternative is using a function named
make_unique, this is in most cases better, because it constructs the object and it helps prevent errors. It also follows the naming convention ofshared_ptr which can be constructed in the same way with the make_shared method.This is how to use
make_unique : >std::unique_ptr< Foo > p2 = std::make_unique< Foo > ();What
make_unique does is that it creates a new unique_ptr that manages a new object of the template type ( Foo in our case. ) It takes the arguments of the make_unique function itself and passes it to the constructor of the new Foo. Then the function returns the unique_ptr it created and the =operator assigns it to the unique_ptr we declared ( p2 )This function requires
C++14, it should have been included in C++11, a simple oversight lead to it not being included in the standard. But don't worry, it's easy to get this to compile:- On
Linux/Mac, you can do this by changing-std=c++11to-std=c++1yin your compile command - On
Windows, newer versionsVisualStudioshould support this without you having to do anything.
Destruction and deallocation
Since the
unique_ptr is a smart pointer, it handles deallocation automatically. In general, this means that the unique_ptr calls delete on the object it holds. This happens when :- The
unique_ptris being destroyed. - Happens when the
unique_ptrgoes out of scope. - You set the
unique_ptrto point to something else
Releasing the object
Releasing the object means that the
unique_ptr stops managing the object. This means you can let a regular pointer "take over" the object the unique_ptr was managing. So when the unique_ptr goes out of scope or is destroyed in any way, the pointer object it was managing wont be affected. Here's an example
Foo* pFoo1 = new Foo();
std::unique_ptr< Foo > fooUPtr( pFoo1);
fooUPtrnow manages pFoo1pFoo1is unchanged
Foo* pFoo2 = nullptr;
pFoo2 = fooUPtr.release();
But now
fooUPtrno longer manages the objectpFoo1points topFoo2now points to the same object aspFoo1
pFoo1/pFoo2 needs to be deleted. Since they both point to the same object, we just need to delete one of them : delete pFoo1; Changing the object being pointed to / reassignment
Changing what the
unique_ptr points to is quite easy. It can be done in two ways :- Using the = operator
- Using the reset() function
These work int the similar fashion
std::unique_ptr< Foo >( new Foo ) foo1;
foo1 = new Foo();Andstd::unique_ptr< Foo >( new Foo ) foo2;
foo2.reset( new Foo() );
Both of these properly deallocates the original object managed by
foo1, calling its destructor in the process. Then the new object returned by new is assigned to foo1 and foo1 now manages it just like it did with the previous object. Transferring ownership
You can't set two
unique_ptrs to manage the same object, but you can transfer ownership from one unique_ptr to another. And you can swap the managed object of two unique_ptrs. Swapping owenership
Swapping ownership can be done in two ways ; a member function, or the
std::swap() function.Member function
The member function is very straight forwards :
std::unique_ptr< Foo >( new Foo() ) foo1;
std::unique_ptr< Foo >( new Foo() ) foo2;
foo1.swap( foo2 );std::swap
std::swap() is a very simple, but also very useful algorithm defined in the algorithm header ( until C++11 and utility since C++11 ). It basically swaps two objects of whatever type you pass as arguments. It can be two ints, two std::strings or two objects of a class/struct or anything else you want. The version used for
unique_ptrs is just an overload of this that uses the above swap( std::unique_ptr ) member function function internally. Here's how to use it : std::unique_ptr< Foo >( new Foo() ) ;
std::unique_ptr< Foo >( new Foo() ) ;
std::swap(foo1, foo2 );So in reality, the member function and
swap() are identical. The result of both of these are as you would expect :foo1now managesfoo2foo2now managesfoo1
Reseting
You can use the
reset() to reset the unique_ptr This means that we change what pointer the unique_ptr manages, deleting the old one. This method can only be used with raw pointers, not smart pointers. Example :
std::unique_ptr< Foo> ptr( new Foo );
foo.reset( new Foo );
What this does
- Crates a new
uniqe_ptrthat manages a newFooobject - Deletes the old managed
Fooobject and replaces it with a new one
Transfering owenership
You can use the
= operator to transfer ownership :std::unique_ptr< Foo >(new Foo() ) foo1;
std::unique_ptr< Foo >(new Foo() ) foo2;
foo1 = foo2;
This takes the pointer from foo2 and gives it to foo1. This means that : foo2no longer handles any object ( like calling )- This is because only one
unique_ptrcan manage an object at a time - The object that
foo1was holding is deleted - Because
is going to be managingfoo1foo2
std::unique_ptr< Foo >(new Foo() ) foo1;
std::unique_ptr< Foo >(new Foo() ) foo2;
foo1.reset( foo2.release() );
Note that this is not the same as swapping two unique_ptr If we swap, the result would be :
In this case the result is.
foo1managesfoo2foo2managesfoo1
foo1managesfoo2foo2doesn't manage any object
Destroying the object
When you want to destroy the object the
unique_ptr manages, there are two ways :- Destructor
- This simply means letting the
unique_ptrgo out of scope - The
reset()method - You can use this with either a
NULLpointer or another pointer, the managed object will get deleted in both cases
The bool operator
The
bool operator dictates what happens when used in a boolean expression like if () . It will return true if the unique_ptr manages an object. If not, it'll return false. In other words ; if, and only if, you can use it, it'll return true, so this is exactly the same as you do when you're checking if a pointer is NULL or nullptrThe other operators
The other operators works just like regular pointers, so I won't discuss them here.
shared_ptr
shared_ptr is, as the name implies, a smart pointer that allows sharing. This means you can have several shared_ptrs that point to the same object. This is not permitted by unique_ptr as we saw in the previous section.Implementation
The
shared_ptr is a bit more complex than unique_ptr. This is because a shared_ptr needs to keep track of how many other shared_ptrs are managing this object, But for unique_ptrs there will always be only one pointer managing the same resource, so there is no need to keep track of the number of other unique_ptrs that are managing this object ( because it's always just one! )In order to keep track of this information,
shared_ptr keeps a pointer to a structure called a control block. This structure has three member variables :-
shared_ptrreference count - How many
shared_ptrs are managing to the object weak_ptrreference count- How many
weak_ptrs are referring to this object - More on
weak_ptrs later - A pointer to the object the
shared_ptrmanages
The
shared_ptr also keeps a pointer to the object it manages. And this is were it gets a little complicated, because this pointer is closely related to the one in the control block. But why? The reason for this is a bit complicated and it has to do with the two ways of creating a shared_ptr, make_shared and constructor. I will discuss this in the next section :Initialization
When it comes to
unique_ptr, the difference between using make_unique and the regular constructor is slight ( that doesn't mean you shouldn't use make_unique as often as you can! ) But in a shared_ptr things are different. As stated above, a
shared_ptr has two elements : a control block and a pointer to the object it manages. Both of these needs to be allocated, but before we get into that, let's look at how we can create a shared_ptr : std::shared_ptr p1(new Foo);
std::shared_ptr< Foo > p2 = std::make_shared< Foo > ();In the first example, we first allocate Foo using new before pass it to the shared_ptr constructor. This means that the share_ptr has no control over the allocation of Foo so all it can do is to create the control block and have it point to Foo. The figure below will show the procedure for when you create a shared_ptr using constructor :Foo and ControlBlock allocated in two stepsAs you can see, the
control block and the Foo needs to be allocated in two steps. First the object, then the control bloc. But if we let
make_shared handle the allocation of Foo, it can allocate both the control block and Foo in one go. It'll look something like this :Foo is now part of the FooControlBlockSo
make_shared creates the object and the control block together in one operation. This makes the operation faster than creating them in two step, but it requires them to be one object, so here Foo is part of the control block itself. make_shared is available in C++11 so you can use it without enabling C++14When not to use make_shared?
There are two cases when you can't use
make_shared :- If you are using a pointer that's already created somewhere else
- Using
make_sharedmeans the object would be re-allocated - Pass the pointer in the constructor instead ( where we passed
new Foo()in the example above ) - If you don't want to use the default
delete - You can't specify a custom deleter using
make_shared - This is a bit complicated, so I won't go into details
Destruction and deallocation
The destructor for
shared_ptr is also a bit different from unique_ptr because an unique_ptr will always be the sole manager of an object ( not other unique_ptr or shared_ptrswill be managing it. ) This means it's always safe to delete, so that's what the unique_ptr will do.But when it comes to
shared_ptrs, we can't do that before we make sure that no other shared_ptrs are managing it. So what we do is that we look on the control block and how many shared_ptrs are managing it. If this is 0, we are the last owner and we can safetely delete it. The
weak_ptr reference count it not checked at this point. I'll get into why in the next section that discusses weak_ptr and how the relate to shared_ptrs.Changing the object being pointed to / reassignment
Similar to
unique_ptrs but here we need to do some extra work. There's two different cases for this ; setting the shared_ptr to be the same as another shared_ptr and setting the shared_ptr to manage a new pointer. In both of these cases, it will decrement the
shared_ptr reference count in the control block. And if this count reaches 0 it will delete the object being pointed to ( but not necessarily the control block, more on this later. ) Assigning to a different shared_ptr
Assigning a
shared_ptr to a different shared_ptr is done using the =operator.Here's a simple example
// Create shared_ptrs
std::shared_ptr< Foo > ptr1 = std::make_shared< Foo >();
std::shared_ptr< Foo > ptr2 = std::make_shared< Foo >();
// Reassign
ptr2 = ptr1;
Result - The original
ptr1'sshared_ptrcount is now0, and the object it manages will be deleted ptr1andptr2will now both manage the same object as the originalptr2with ashared_ptrcount of2
Assigning shared_ptr to a new object
Assigning a
shared_ptr to a new object/pointer is done using the reset() function :Here's a simple example
Result// Create shared_ptrs
std::shared_ptr< Foo > sharedPtr = std::make_shared< Foo >();
Foo* rawPtr = new Foo();
// Reassign
sharedPtr_ptr.reset( rawPtr );
- The
shared_ptrreference count forsharedPtris decremented as if we were calling the destructor. - The
FooobjectsharedPtrwas originally manging may get deleted sharedPtrnow manages the objectrawPtrpoints to.
As you can see from the examples, you use
opeator= for reassigning to another shared_ptr but reset() for reassigning to a different raw pointer. You can't use them the other way around. This can help prevent bugs by giving an error if the programmer uses the wrong versions. There is a way you can use
operator= to assign to a new pointer; using make_shared< Foo > to create a new object : std::shared_ptr< Foo >( new Foo ) foo2;
foo2 = make_shared< Foo >();
This works because make_shared creates and returns a fully constructed shared_ptr ( just like make_unique described above ) and the =operator assigns it just like in the example above.Swapping
The syntax for swapping
shared_ptrs is the exact same as for swapping two unique_ptrs : std::shared_ptr< Foo >( new Foo ) foo2;
std::shared_ptr< Foo >( new Foo ) foo2;
Member function :foo1.swap( foo2 );std::swap :std::swap( foo1, foo2 );This will, as you would expect, swap both the control block and pointer for both the shared_ptr. It needs to swap the control block since this is what keeps tracks of the number of references to the pointer so these needs to be consistent. The bool operator
The
bool operator dictates what happens when used in a boolean expression like if () . It will return true if the shared_ptr manages an object. If not, it'll return false. In other words ; if, and only if, you can use it, it'll return true, so this is exactly the same as you do when you're checking if a pointer is NULL or nullptrThis is exactly the same as for
unique_ptr.The other operators
The other operators, just like with
unique_ptr, works just like regular pointers, so I won't discuss them here.weak_ptr
As mentioned in the introduction,
weak_ptr doesn't actually manage a pointer. It just holds a non-owning ( "weak" ) reference to an object that is managed by a shared_ptr. It also keeps a pointer to the control block ( the exact same as the one in the shared_ptr who manages the object. This means it has to be created from a shared_ptr so that it can get a pointer to the control block. wrak_ptr and the control block
The control block, as we saw in the previous section, keeps a count of both
shared_ptr and weak_ptr who's using the object. We also saw that the object will get deleted if the count of shared_ptrs using the object is 0 regardless of how what the weak_ptrs count is. This is part of the point of weak_ptr; it is not supposed to keep objects alive except for in situations we explicitly tell it to. But even though the managed object will get deleted if the count of
shared_ptrs is 0, the control block will remain intact. The control block will only be deleted if both the conut of shared_ptr and weak_ptr uses. This is because the weak_ptr uses the control block to check if the object is alive. Creating a weak_ptr
There are two ways of creating a
weak_ptr from a shared_ptr: constructor and =operator. This is very straight forwards : std::shared_ptr< int > sharedPtr = std::make_shared( 42 );
// 1. Constructor
std::weak_ptr< int > weakPtr1( sharedPtr );
// 2. = operator
std::weak_ptr< int > weakPtr2 = sharedPtr; You can also create one from another weak_ptr :// 3. Constuctor - weak_ptr
std::weak_ptr< Foo > weakPtr3( weakPtr1 );
// 4. = operator - weak_prt
std::weak_ptr< Foo > weakPtr4 = weakPtr2;
/code>All of these will set up the pointer and the control block and increment weak count by 1. Creating a
weak_ptr from a raw pointer won't work simply because it isn't designed to be managing a pointer by itself. And you can't use them with unique_ptrs because unique_ptrs are supposed to be the sole owner. Reseting a weak_ptr
The object a
weak_ptr can be reset ( so that it no longer references any object ) using the destructor or the reset() function.
// Set up a weak_ptr
std::shared_ptr< Foo > sharedPtr = std::make_shared< Foo >();
std::weak_ptr< Foo > weakPtr = sharedPtr;
// Reset it
weakPtr.reset()
Using a weak_ptr
weak_ptr has the function lock(). What this function does is that it makes a shared_ptr of itself. This shared_ptr will work exactly as any other shared_ptrs maintaining the object. It will increase the shared_ptr count, so this shared_ptr will keep the object alive. If the object has been deleted, it will still return a
shared_ptr. But this shared_ptr doesn't point to anything, it's essentially a nullptr. It will return false if you use it in a if or loop condition, just like a regular shared_ptr or unique_ptr that doesn't manage an object.This function is the crucial part of the
weak_ptr. It enables us to keep weak_ptrs that refers to object maintained by a shared_ptr without preventing it from being deleted and we can still use it safely when we want. So let's look at an example :
Usage 1
Here everything works as intended. The
weak_ptr refers to sharedPtr which has a valid object inside this scope. lock()returns ashared_ptrthat manages the same object assharedPtr- The returned
shared_ptris valid so it enters the if - While in this if, the
shared_ptr,fromWeakPtr1keeps the object alive, even if all othershared_ptrshould be destroyed in other threads in the meantime
Usage 2
Our
shared_ptrs has gone out of scope ( both the original and the one we created from the weak_ptr and the object has been deleted. But the weak_ptr, weakPtr still exists as it was declared out of the scope. So we try to use it again :- We create our
shared_ptrfrom theweak_ptra - The
sharedis created, but since the original object was deleted, this is essentially a nullptr - The
shared_ptrreturns false and it doesn't enter the if.
Leaving the scope
Finally we leave the scope and we delete
weakPtr. Now the weak_ptr count will be 0 and the control block will be deleted ( shared_ptr count was already 0 )Releasing the object
A
weak_ptr can't release the object because it doesn't have control over it, and the object might be NULL. For this reason, weak_ptr does not have a release() function. The closest thing is the reset() function described above.Swapping
The syntax for swapping
weak_ptrs is the exact same as for swapping two shared_ptrs and code>unique_ptrs: std::weak_ptr< Foo >( new Foo ) foo2;
std::weak_ptr< Foo >( new Foo ) foo2;
Member function :foo1.swap( foo2 );std::swap :std::swap( foo1, foo2 );This will, as you would expect, swap both the control block and pointer for both the
weak_ptrs. It needs to swap the control block since this is what keeps tracks of the number of references to the pointer so these needs to be consistent.Changing the object being pointed to / reassignment
Similar to
shared_ptrs but with a few differences :- Since we are not managing the object, we don't need to worry about deleting it.
- It might already have been deleted at this point, but this will not cause the opeartion to fail
- Decrement count for
weak_ptr, notshared_ptr - If
weak_ptrcount reaches0and the count forshared_ptris also 0, we delete the control block - Now we can set the pointer to the object and control block
- Both of these will already have been created by a
shared_ptr
You can create reassign a
weak_ptr to both a shared_ptr and another weak_ptr.For a full list of my tutorials / posts, click here. Feel free to comment if you have anything to say, spot any errors or have any suggestion or questions. I always appreciate getting comments.

Ingen kommentarer:
Legg inn en kommentar