Protecting your code with a smart pointer

The code we just described is fully functional, but, it can be strengthened, specifically with the function, AlbumDao::albums(). In this function, we iterate through the database rows and create a new Album to fill a list. We can zoom in on this specific code section:

QVector<Album*> list; 
while(query.next()) { 
    Album* album = new Album(); 
    album->setId(query.value("id").toInt()); 
    album->setName(query.value("name").toString()); 
    list.append(album); 
} 
return list; 

Let's say that the name column has been renamed to title. If you forget to update query.value("name"), you might run into trouble. The Qt framework does not rely on exceptions, but this cannot be said for every API available in the wild. An exception here would cause a memory leak: the Album* album function has been allocated on the heap but not released. To handle this, you would have to surround the risky code with a try catch statement and deallocate the album parameter if an exception has been thrown. Maybe this error should bubble up; hence, your try catch statement is only there to handle the potential memory leak. Can you picture the spaghetti code weaving in front of you?

The real issue with pointers is the uncertainty of their ownership. Once it has been allocated, who is the owner of a pointer? Who is responsible for deallocating the object? When you pass a pointer as a parameter, when does the caller retain the ownership or release it to the callee?

Since C++11, a major milestone has been reached in memory management: the smart pointer feature has been stabilized and can greatly improve the safety of your code. The goal is to explicitly indicate the ownership of a pointer through simple template semantics. There are three types of smart pointer:

  • The unique_ptr pointer indicates that the owner is the only owner of the pointer
  • The shared_ptr pointer indicates that the pointer's ownership is shared among several clients
  • The weak_ptr pointer indicates that the pointer does not belong to the client

For now, we will focus on the  unique_ptr pointer to understand smart pointer mechanics.

A unique_ptr pointer is simply a variable allocated on the stack that takes the ownership of the pointer you provide with it. Let's allocate an Album with this semantic:

#include <memory> 
void foo() 
{ 
    Album* albumPointer = new Album(); 
    std::unique_ptr<Album> album(albumPointer); 
    album->setName("Unique Album"); 
} 

The whole smart pointer API is available in the memory header. When we declared album as a unique_ptr, we did two things:

  • We allocated on the stack a unique_ptr<Album>. The unique_ptr pointer relies on templates to check at compile time the validity of the pointer type.
  • We granted the ownership of albumPointer memory to album. From this point on, album is the owner of the pointer.

This simple line has important ramifications. First and foremost, you do not have to worry anymore about the pointer life cycle. Because a unique_ptr pointer is allocated on the stack, it will be destroyed as soon as it goes out of scope. In this example, when we exit foo()album will be removed from the stack. The unique_ptr implementation will take care of calling the Album destructor and deallocating the memory.

Secondly, you explicitly indicate the ownership of your pointer at compile time. Nobody can deallocate the albumPointer content if they do not voluntarily fiddle with your unique_ptr pointer. Your fellow developers will also know with a single glance who is the owner of your pointer.

Note that, even though album is a type of unique_ptr<Album>, you can still call Album functions (for example, album->setName()) using the -> operator. This is possible thanks to the overload of this operator. The usage of the unique_ptr pointer becomes transparent.

Well, this use case is nice, but the purpose of a pointer is to be able to allocate a chunk of memory and share it. Let's say the foo() function allocates the album unique_ptr pointer and then transfers the ownership to bar(). This would look like this:

void foo() 
{ 
    std::unique_ptr<Album> album(new Album()); 
    bar(std::move(album)); 
} 
 
void bar(std::unique_ptr<Album> barAlbum) 
{ 
   qDebug() << "Album name" << barAlbum->name(); 
} 

Here, we introduce the std::move() function: its goal is to transfer the ownership of a unique_ptr function. Once bar(std::move(album)) has been called, album becomes invalid. You can test it with a simple if statement: if (album) { ... }.

From now on, the bar() function becomes the owner of the pointer (through barAlbum) by allocating a new unique_ptr on the stack and it will deallocate the pointer on its exit. You do not have to worry about the cost of a unique_ptr pointer, as these objects are very lightweight and it is unlikely that they will affect the performance of your application.

Again, the signature of bar() tells the developer that this function expects to take the ownership of the passed Album. Trying to pass around unique_ptr without the move() function will lead to a compile error.

Another thing to note is the different meanings of the . (dot) and the -> (arrow) when working with a unique_ptr pointer:

  • The -> operator dereferences to the pointer members and lets your call function on your real object
  • The . operator gives you access to the unique_ptr object functions

The unique_ptr pointer provides various functions. Among the most important are:

  • The get() function returns the raw pointer. The album.get() returns an Album* value.
  • The release() function releases the ownership of the pointer and returns the raw pointer. The album.release() function returns an Album* value.
  • The reset(pointer p = pointer()) function destroys the currently managed pointer and takes ownership of the given parameter. An example would be the barAlbum.reset() function, which destroys the currently owned Album*. With a parameter, barAlbum.reset(new Album()) also destroys the owned object and takes the ownership of the provided parameter.

Finally, you can dereference the object with the * operation, meaning *album will return an Album& value. This dereferencing is convenient, but you will see that the more a smart pointer is used, the less you will need it. Most of the time, you will replace a raw pointer with the following syntax:

void bar(std::unique_ptr<Album>& barAlbum); 

Because we pass the unique_ptr by reference, bar() does not take ownership of the pointer and will not try do deallocate it upon its exit. With this, there is no need to use move(album) in foo(); the bar() function will just do operations on the album parameter but will not take its ownership.

Now, let's consider shared_ptr. A shared_ptr pointer keeps a reference counter on a pointer. Each time a shared_ptr pointer references the same object, the counter is incremented; when this shared_ptr pointer goes out of scope, the counter is decremented. When the counter reaches zero, the object is deallocated.

Let's rewrite our foo()/bar() example with a shared_ptr pointer:

#include <memory> 
void foo() 
{ 
    std::shared_ptr<Album> album(new Album()); // ref counter = 1 
    bar(album); // ref counter = 2 
} // ref counter = 0 
 
void bar(std::shared_ptr<Album> barAlbum) 
{ 
   qDebug() << "Album name" << barAlbum->name(); 
} // ref counter = 1 

As you can see, the syntax is very similar to the unique_ptr pointer. The reference counter is incremented each time a new shared_ptr pointer is allocated and points to the same data, and is decremented on the function exit. You can check the current count by calling the album.use_count() function.

The last smart pointer we will cover is the weak_ptr pointer. As the name suggests, it does not take any ownership or increment the reference counter. When a function specifies a weak_ptr, it indicates to the callers that it is just a client and not an owner of the pointer. If we re implement bar() with a weak_ptr pointer, we get:

#include <memory> 
void foo() 
{ 
    std::shared_ptr<Album> album(new Album()); // ref counter = 1 
    bar(std::weak_ptr<Album>(album)); // ref counter = 1 
} // ref counter = 0 
 
void bar(std::weak_ptr<Album> barAlbum) 
{ 
   qDebug() << "Album name" << barAlbum->name(); 
} // ref counter = 1 

If the story stopped here, there would not be any interest in using a weak_ptr versus a raw pointer. The weak_ptr has a major advantage for the dangling pointer issue. If you are building a cache, you typically do not want to keep strong references to your object. On the other hand, you want to know if the objects are still valid. By using weak_ptr, you know when an object has been deallocated. Now, consider the raw pointer approach: your pointer might be invalid but you do not know the state of the memory.

There is another semantic introduced in C++14 that we have to cover: make_unique. This keyword aims to replace the new keyword and construct a unique_ptr object in an exception-safe manner. This is how it is used:

unique_ptr<Album> album = make_unique<Album>(); 

The make_unique keyword wraps the new keyword to make it exception-safe, specifically in this situation:

foo(new Album(), new Picture()) 

This code will be executed in the following order:

  1. Allocate and construct the Album function.
  2. Allocate and construct the Picture function.
  3. Execute the foo() function.

If new Picture() throws an exception, the memory allocated by new Album() will be leaked. This is fixed by using the make_unique keyword:

foo(make_unique<Album>(), make_unique<Picture>()) 

The make_unique keyword returns a unique_ptr pointer; the C++ standard committee also provided an equivalent for shared_ptr in the form of make_shared, which follows the same principle.

All these new C++ semantics try very hard to get rid of new and delete. Yet, it may be cumbersome to write all the unique_ptr and make_unique stuff. The auto keyword comes to the rescue in our album creation:

auto album = make_unique<Album>() 

This is a radical departure from the common C++ syntax. The variable type is deduced, there is no explicit pointer, and the memory is automatically managed. After some time with smart pointers, you will see fewer and fewer raw pointers in your code (and even fewer delete, which is such a relief). The remaining raw pointers will simply indicate that a client is using the pointer but does not own it.

Overall, C++11 and C++14 smart pointers are a real step up in C++ code writing. Before them, the bigger the code base, the more insecure we felt about memory management. Our brain is just bad at properly grasping complexity at such a level. Smart pointers simply make you feel safe about what you write. On the other hand, you retain full control of the memory. For performance-critical code, you can always handle the memory yourself. For everything else, smart pointers are an elegant way of explicitly indicating your object's ownership and freeing your mind.

We are now equipped to rewrite the little insecure snippet in the AlbumDao::albums() function. Update AlbumDao::albums() like so:

// In AlbumDao.h 
std::unique_ptr<std::vector<std::unique_ptr<Album>>> albums() const; 
 
// In AlbumDao.cpp 
unique_ptr<vector<unique_ptr<Album>>> AlbumDao::albums() const 
{ 
    QSqlQuery query("SELECT * FROM albums", mDatabase); 
    query.exec(); 
    unique_ptr<vector<unique_ptr<Album>>> list(new vector<unique_ptr<Album>>()); 
    while(query.next()) { 
        unique_ptr<Album> album(new Album()); 
        album->setId(query.value("id").toInt()); 
        album->setName(query.value("name").toString()); 
        list->push_back(move(album)); 
    } 
    return list; 
} 

Wow! The signature of the album() function has turned into something very peculiar. Smart pointers are supposed to make your life easier, right? Let's break it down to understand a major point of smart pointers with Qt: container behavior.

The initial goal of the rewrite was to secure the creation of album. We want the list to be the explicit owner of the album. This would have changed our list type (that is albums() return type) to QVector<unique_ptr<Album>>. However, when the list type is returned, its elements will be copied (remember, we previously defined the return type to QVector<Album>). A natural way out of this would be to return a QVector<unique_ptr<Album>>* type to retain the uniqueness of our Album elements.

Behold, here lies a major pain: the QVector class overloads the copy operator. Hence, when the list type is returned, the uniqueness of our unique_ptr elements cannot be guaranteed by the compiler and it will throw a compile error. This is why we have to resort to a vector object coming from the standard library and write the long type: unique_ptr<vector<unique_ptr<Album>>>.

Take a look at the official response for support of the unique_ptr pointer in the Qt container. It is clear beyond any possible doubt:  http://lists.qt-project.org/pipermail/interest/2013-July/007776.html. The short answer is: no, it will never be done. Do not even mention it. Ever.

If we translate this new albums() signature into plain English it will read: the album() function returns a vector of Album. This vector is the owner of the Album elements it contains and you will be the owner of the vector.

To finish covering this implementation of albums(), you may notice that we did not use the auto and make_unique keywords for the list declaration. Our library will be used on a mobile in Chapter 13Dominating the Mobile UI, and C++14 is not yet supported on this platform. Therefore, we have to restrain our code to C++11.

We also encounter the use of the move function in the instruction list->push_back(move(album)). Until that line, the album is "owned" by the while scope, the move gives the ownership to the list. At the last instruction, return list, we should have written move(list), but C++11 accepts the direct return and will automatically make the move() function for us.

What we covered in this section is that the AlbumDao class is completely matched in PictureDao. Please refer to the source code of the chapter to see the full PictureDao class implementation.