auto + const + smart pointer = bad mix?
const
is a feature that has been appreciated by C++ developers for decades of good services, to make code more robust by preventing accidental modifications.
Smart pointers have been around for a long time too, and simplified the life cycle of many objects along with the life balance of many developers across the years.
auto
is a more recent feature (C++11), designed to make code simpler, and it has been promoted for years for us to use it almost always.
So when we put auto
, const
and a smart pointer together, we should expect it to produce a great mix of simple, robust and expressive code.
But this combination can lead to deceptive code rather than expressive code. As in code that looks like it does something, but it fact doesn’t. And deceptive code is one of the most dangerous sorts of code.
auto + const + pointer
When declaring an object, using auto
and const
implies that the object is indeed const
:
auto const numbers = std::vector<int>{1, 2, 3, 4, 5};
The above vector numbers
is const
: we can’t add, remove or modify anything to it otherwise the code wouldn’t compile. If this vector is meant to be an input, it prevents us to modify it by accident and create a bug.
Now consider the following case: assigning a pointer into an auto const
value:
Thing* getSomething(); auto const thing = getSomething();
What does this code look like? It says that thing
is const
. But thing
is a pointer, which means that thing
cannot point to anything else than whatever getSomething
has returned. This is the equivalent of:
Thing* const thing = getSomething();
The pointer is const, but not the value it points to.
But when using thing
in business code, do you really care about the value of the pointer? If the point of using thing
is to reach to the object it points to, like it’s often the case, you don’t. The role of thing
is to embody the object is points to, and it so happens that you’re given a pointer to manipulate it.
Therefore, what it looks like to me is that the code suggests that we’re manipulating a const
Thing
, and not a const
pointer to Thing
. True, this is not what is happening, but when reading code you don’t check out every prototype of every function that is called. All the more so if the prototype of getSomething
is not in the immediate vicinity (which it generally isn’t):
auto const thing = getSomething();
This code screams that you are protected by a read-only thing
, whereas it’s just a read-only pointer to a modifiable object. Doesn’t it look deceptive to you?
One way to get around this problem could be to use auto const*
, to make the pointed-to object const
:
auto const* thing = getSomething();
Or is it a case for the Hungarian notation to come back?
auto const pThing = getSomething();
Ew, no, we don’t like the Hungarian notation.
But you may be thinking, who returns a raw pointer from a function anyway? We even evoked the possibility of removing raw pointers from the C++ (okay, it was on the 1st of April but still, the idea didn’t come out of nowhere). We should use smart pointers now, right?
Right, we should. But first, there is still legacy code out there that hasn’t caught up yet, and it’s safe to say that there will still be some for a while.
And second, smart pointers suffer from the same problem, but worse. Let’s see why.
auto + const + smart pointer
Let’s modernize the interface of getSomething
and make it return a smart pointer to express that it relinquishes the ownership of the objet to its caller:
std::unique_ptr<Thing> getSomething();
Our calling code looks like this:
auto const thing = getSomething();
Even if in terms of ownership the code is much more robust, in terms of what is const
and what is not, the situation is identical to the one with raw pointers.
Indeed, in the above code the smart pointer is const
, which we rarely care about, but the object it points to is not. And the code gives that false feeling of protection by luring a reader passing by into thinking that the object really used by the code (likely the Thing
the smart pointer points to) is const
and that all is safe.
What is worse with smart pointers is that there is no way to add info around the auto
. With a raw pointer we could resort to:
auto const* thing = getSomething();
But with a smart pointer, we can’t.
So in this case, I guess the best option is to remove the const
altogether, to avoid any confusion:
std::unique_ptr<Thing> getSomething(); auto thing = getSomething();
Have you encountered this problem in your code? How did you go about it? All your comments are welcome.
You may also like
- Smart developers use smart pointers
- The formidable const reference that isn’t const
Share this post!