Jonathan Boccara's blog

auto + const + smart pointer = bad mix?

Published July 12, 2019 - 0 Comments

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 autoconst 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

Don't want to miss out ? Follow:   twitterlinkedinrss
Share this post!Facebooktwitterlinkedin