auto is probably one of the best thing ever happened to C++.

And I say this despite having been extremely wary of it for a while: coming from C, I felt that lack of a clear indication of a variable type scary, error-prone and flat-right wrong.

But it didn’t take too much to understand why auto is a great addition; if you’ve read my previous posts, you know how much I care about Code Clarity, and despite what some people say about auto making code less clear, I think that the number of cases if which it makes things way easier to read outweighs, by a huge margin, the few cases in which we are better without using it.

And still, after years of using it, I keep falling victim of an annoying aspect of auto that I will never learn. Let me ask you a question: given a function with the following signature

someClass& foo();

called as follows:

auto bar = foo();

What do you expect to be the type of bar?

  • a) someClass
  • b) someClass&

If you answered b, welcome to my world!

It is actually someClass!

Surprised Pikachu

and if you want to get a reference, you need to

auto& bar = foo();

Not convinced? Check out this very simple example.

You can see how this can be tricky when expecting to modify some variable and finding out, later in the code, that its value has not been changed!

But there is maybe an even worse side of this: if you are not passing a reference, that must mean that you are copying the object that is returned! And we know that can be really bad!

Let’s take the example we used on last week’s post and expand on it:

int emptyConstructor = 0;
int copyConstructor = 0;

class Person
{
private:
    std::string firstName_, lastName_;
public:
    Person() {
        ++emptyConstructor;
    }

    // Copy constructor
    Person(const Person& p2) {
        firstName_ = p2.firstName_;
        lastName_ = p2.lastName_;
        ++copyConstructor;
    }
};

This is our Person class which defines an empty constructor and a copy constructor; we then store somewhere a vector<Person>

class PersonHolder
{
    public:
    PersonHolder(int n)
    {
        p_.resize(n);
    }
    Person& getPerson(int n)
    {
        return p_[n];
    }
    std::vector<Person> p_;
};

See how this class holds a vector<Person> and has a function that returns a Person& of an element in the vector (let’s forget about input checking for the sake of simplicity).

Let’s see that in action:


int main()
{
    const int N = 10000;
    PersonHolder p(N);
    
    for (int i=0; i<N; ++i)
    {
        auto person = p.getPerson(i);
    }
  
    std::cout << "---------- Using auto ---------------" << std::endl;
    std::cout << "Empty constructor called " << emptyConstructor << " times.\n";
    std::cout << "Copy constructor called " << copyConstructor << " times.\n";
    
    emptyConstructor = 0;
    copyConstructor = 0;
    
    PersonHolder p2(10000);
    for (int i=0; i<N; ++i)
    {
        auto& person = p2.getPerson(i);
    }
    std::cout << "---------- Using auto& ---------------" << std::endl;
    std::cout << "Empty constructor called " << emptyConstructor << " times.\n";
    std::cout << "Copy constructor called " << copyConstructor << " times.\n";

    return 0;
}

If you run this code (and you can do it from this link), you will see that, when using auto, instead of getting the reference to the element in the vector, we are actually creating a new one by copying it, with the result that we are calling 10000 times the empty constructor (when resizing the vector) and other 10000 times the copy constructor!

This, of course, does not happen if we use auto& instead!

Of course, in this example, copying is not such an expensive operation and we might not really care (except for what we said before, about not being able to modify what we actually want to modify!), but as always with cases like this, the problem arises when copying a class is computationally very expensive (think about copying the class PersonHolderinstead, together with its potentially massive vector!).

Conclusions

auto is an amazing feature: it frees us from manually specifying long, cumbersome to write types. It focus the attention of whoever reads the code to more important parts.

However, me must be really mindful of what it does or how it behaves to prevent misusing it.


Nikolas De Giorgis

Basically, a software developer. Less basically, I'm passionate about photography, music, sport, reading, traveling and many, many other things!