Smart Pointers as Pointers
Learn about the relation between const and smart pointers acting as pointers.
We'll cover the following...
There are two ways to approach smart pointers. One way is to consider that smart pointers are effective pointers. Either the pointer or the pointed value can be const. Or both.
In another perspective, we consider that smart pointers are class-type objects. Basically, they are wrapping pointers. As a smart pointer is an object, the rule of thumb says that it can be passed around as a const reference. We are going to see that in most cases, it’s a bad idea.
Let’s examine both perspectives.
const and smart pointers as pointers
Smart pointers are effective pointers because they clean up after themselves. Therefore, we can use const both with the pointer itself or the pointed value.
There are different smart pointers, but as our default choice should be std::unique_ptr, we’ll use this one throughout our examples. Except when we explicitly need a shared pointer to demonstrate specific concepts.
const std::unique_ptr<T>
In this case, it’s the pointer that is const and not what we point to. It means that we cannot reset the pointer or change what it points to. At the same time, the value it points to can be modified.
It’s worth noting that a const unique_ptr provides limited options. We cannot return it because it requires moving away from the pointer. Therefore, the following code will not work.
Meanwhile, a const return type is not a problem starting from C++17. The snippet below produces the same error on C++14 as the above one. However, it works with C++17:
The difference between the two snippets is that the p is not declared const.
std::unique_ptr<const T>
In this case, the value the pointer points to is const, but the pointer itself is mutable. In other words, we cannot change the value of the pointed data, but we can change what the pointer points to.
In the expression std::make_unique<const int>(42) the const is not mandatory, the code will compile even if we forget the const. However, it’s best to remember it. If we check it in compiler explorer, missing the const results in an extra move constructor call, we have to destruct the temporary object:
In case we don’t forget the const within std::make_unique, the above code simplifies to:
To summarize, if we want a const smart pointer, use const both on the left and the right side, given that you use the std::make_* functions. It’s often better to use auto.
const std::unique_ptr<const T>
In this case, it’s a combination of the two consts. Both the pointed value and the (smart) pointer are const; therefore, no change is accepted.
Remember to use the const on both sides!