Get in touch
Thank you
We will get back to you as soon as possible

4.7.2014

7 min read

4. Haters gonna hate or life without pointers in C++11 times

Introduction

In my previous article I have described how do not use pointers. Before C++11 times it wasn't so easy task, but it's possible. With coming C++11 standard life of programmer became better. Heretofore programmer should use third-party library to use RAII-like entities which are smarter than std::auto_ptr in managing memory. Due to the fact that std::auto_ptr doesn't have reference counting and doesn't provide good sharing data between entities in code.

Now C++ standard library has such cool stuff as: std::unique_ptr, std::shared_ptr, std::weak_ptr. Also I am going to talk about std::array little bit, because it is more convenient instead of static arrays.

Enumerated entities suit for different cases and give a programmer good tools for resolving  issues.

std::unique_ptr instead of std::auto_ptr

or saver life with unique variable

This smart pointer was born as replacement for std::auto_ptr. That's why the last one is deprecated in this standard. std::unique_ptr has similar interface as std::auto_ptr, but it's better designed and implemented what allows to use this smart pointer when std::auto_ptr can't be used. Let's talk about everything in turn.

std::unique_ptr has same user cases as std::auto_ptr. Remind you:

  • wrap class field which allocated on heap;
  • wrap local variable which allocated on heap;
  • wrap input argument which allocated on heap;
  • wrap returned value on heap.

From name of smart pointer you should have guessed that only single one std::unique_ptr holds managed instance and it can't be transferred through assignment operator or copy constructor because it is forbidden. But it can be done by move semantics of C++11. See:

{
  std::unique_ptr<Resource> resource0;
  {
    std::unique_ptr<Resource> resource1(new Resource);
    // forbidden and causes compile error,
    // because of: operator=(const unique_ptr&) = delete
    resource0 = resource1;
    // But this one works.
    // Managed instance of resource1 is moved to resource0.
    resource0 = std::move(resource1);
    resource1->doSomething(); // UB
  }
  resource0->doSomething();
} //'new Resource' is freed here.

This works with std::move() because operator =(std::unique_ptr&) is forbidden, but operator =(std::unique_ptr&&) is not. std::unique_ptr&& is a reference to r-value of std::unique_ptr instance. More details about such references to r-value you can read this. It is very important topic about C++11, but is not directly related to this article.

Unlike std::auto_ptr unique smart pointer can be used for arrays, because its template declaration has specification for arrays. It correctly deallocates memory, which is allocated with operator new []. Check it out:

{
  std::size_t size = 10;
  std::unique_ptr<int[]> array(new int[size]);
 
  for(std::size_t i = 0; i < size; ++i)
  {
    array[i] = i*i;
  }
} // “new int[size]” is freed by delete[] here.

So instead of using std::vector like I wrote in previous article in C++11 you can use std::unique_ptr<T[]> for holding dynamic arrays in cases if std::vector doesn't fit your needs. If you want, you can get pointer to allocated memory with help of method std::unique_ptr::get(). But don't forget – when unique smart pointer goes out of scope, it frees memory.

std::shared_ptr helps to share instance with friends or finally we have standard reference counting

std::unique_ptr does not allow sharing instance. Same as std::auto_ptr it doesn't have reference counting. For such target you have to use std::shared_ptr. It counts how many shared pointers hold same instance and destroy instance only when the last referenced pointer is destroyed or it is reset with new value through std::shared_ptr::reset(T*).

Typical implementation of shared pointer has fields with managed instance and control block. Control block holds different information about allocation and deallocation of instance and number of smart pointers which point to the same managed instance.

Besides these control block holds pointer to managed instance or this instance itself, in case of using std::make_shared which results in object and control block sharing the same memory block. See the following code:

std::shared_ptr extraAllocation(new Resource());   std::shared_ptr singleAllocation = std::make_shared();

In the first case instance and control block are allocated separately. In such way you have two allocations and control block holds pointer to created instance. By using std::make_shared() it is done at one time. It creates managed instance and control block at single allocation. It means – it takes less time and has exception safety. Because of that second way is more preferable. In C++14 there will be similar method for unique smart pointer called std::make_unique(). It is added just for convenience. It doesn't have double allocation, but it is awesome to use such help functions.

As I said managed instance of shared smart pointer dies when last such pointer to instance is gone away. Lets see the following code for proving which uses std::shared_ptr::use_count() method to show how many pointers manage same instance:

// std::shared_ptr<T> is passed by value, which means copying of input variable,
// but copy manages same instance of Resource as argument of function.
void PassSharedPtr(std::shared_ptr<Resource> ptr)
{
  std::cout << ptr.use_count() << std::endl;
}
 
...
 
  // lastHolder doesn't keep anything.
  std::shared_ptr<Resource> lastHolder;
 
  // Prints 0.
  std::cout << lastHolder.use_count() << std::endl;
 
  {
    std::shared_ptr<Resource> initHolder = std::make_shared<Resource>();
    // Prints 1.
    std::cout << initHolder.use_count() << std::endl;
 
    std::shared_ptr<Resource> newHolder = std::make_shared<Resource>();
 
    // Exchanges managed instance between initHolder and newHolder.
    newHolder.swap(initHolder);
    // Prints 1.
    std::cout << initHolder.use_count() << std::endl;
    // Prints 1.
    std::cout << newHolder.use_count() << std::endl;
 
    // secondHolder keeps same managed instance as initHolder.
    std::shared_ptr<Resource> secondHolder = initHolder;
    // Prints 2.
    std::cout << initHolder.use_count() << std::endl;
 
    // Resets initHolder. Now it doesn't keep anything.
    initHolder.reset();
    // Prints 0.
    std::cout << initHolder.use_count() << std::endl;
 
    // Prints 1.
    std::cout << newHolder.use_count() << std::endl;
 
    lastHolder = newHolder;
    // Prints 2.
    std::cout << newHolder.use_count() << std::endl;
 
    // Prints 3 in function.
    PassSharedPtr(newHolder);
  } // created resource for newHolder destroys here.
 
  // Prints 1.
  std::cout << lastHolder.use_count() << std::endl;
} // Here lastHolder destroys instance which was created for initHolder.

As you see std::shared_ptr keeps instance alive while some smart pointer still points to it. So main use case for std::shared_ptr is sharing some object in different part of program and keeping it alive. By the way std::shared_ptr is thread safe type. So it can be used for sharing data in multi-threaded applications, but don't overuse it, because it is significant performance penalty.

std::weak_ptr could be dead

or never mind when it is alive

If std::smart_ptr cares about keeping instance alive, std::weak_ptr works in opposite way. This smart pointer doesn't uphold lifetime of addressed instance. Because of this weak smart pointer is used for cases when you need access to instance when it is still alive. This type of smart pointer is constructed from std::shared_ptr. Take a look at next code:

std::weak_ptr<Resource> weak;

{
  std::shared_ptr<Resource> shared = std::make_shared<Resource>();

  weak = shared;

  if (std::shared_ptr<Resource> resource = weak.lock())
  {
    std::cout << "Using shared_ptr which extends life of instance" << std::endl;
    resource->doSomething();
  }
}

// Prints "Resource is dead".
  std::cout << (weak.expired() ? "Resource is dead" : "Resource is alive") << std::endl;

As you see std::weak_ptr is constructed from std::shared_ptr. In my example I did it with assignment operator, but it can be done by passing shared pointer to constructor of weak pointer as well.

When you are going to use managed instance from weak pointer you have to call method std::weak_ptr::lock(). It returns std::shared_ptr which extends lifetime of an instance while you are using it. If instance is already destroyed, returned shared pointer will be empty. Also you can use method std::weak_ptr::expired() which indicates whether managed instance still presents or you can forget about its existence.

Underlining that was said std::weak_ptr uses for delivering or holding managed instance somewhere for using it without worrying whether it is alive or not. Also its implementation and usage are based on thread safe smart shared pointer, so std::weak_ptr has same ability in multi-threaded applications.

std::array as cool as static arrays

or avoid using static C-style arrays

Of course you can use std::unique_ptr<T[]> for arrays as I have shown above. But C++11 also provides simpler and quicker container for static arrays, which not worse than C-style arrays. I am talking about std::array<T, N>. This container provides storing of fixed range of instances and different methods for enumerating them with iterators.

The following code shows how such array can be used with std::sort() function:

std::array<int, 3> array = { 2, -1, 0 };
std::sort(std::begin(array), std::end(array));

// Prints: -1 0 2
for (int value: array)
{
  std::cout << " " << value;

As you probably noticed std::array<T, N> has two template arguments. T is type of stored instances and N is number of such instances. In shown example I have defined static array with integer values which size is 3. Through std::sort() I have sorted values of this static array. I have used std::begin() and std::end() because it is more preferably in C++11, because implementation of such methods encapsulate method of obtaining first or last iterators of containers.

There is std::dynarray in proposal for C++14 standard. It allows to handle dynamic arrays in same way. So it can be totally used instead of std::vector in such cases. It is simpler than std::vector in using. Unfortunately, it looks like it won't make it in the new standard, but you are aware of this now. Someday it will come as include header.

Conclusion

In this article I have told about C++11 stuff which can be used to avoid using raw pointers. C++11 gives a lot of smart pointers for different goals, such as: std::unique_ptr, std::unique_ptr<T[]>, std::shared_ptr and std::weak_ptr. std::unique_ptr is used for owning single instance and handling its lifetime. std::unique_ptr<T[]> has similar means, but for set of instances. std::shared_ptr is used for managing instance between different part of application and extends lifetime of owned instance. std::weak_ptr is used when you want to work with instance without owning it. Also as separate entity I noticed std::array<T, N>, which can be used instead of static C-style arrays.

Here you can find links to previous articles of this series:

As usual here is Github repository with code of those articles.

0 Comments
name *
email *