Pass by Reference vs Pass by Value in C++

I work in an undergraduate T.A. role for a computer science sequence that teaches students programming concepts and data structures in C++. In each course, students have to take a proficiency demo — a closed book, closed notes, live coding exam where they must solve a technical interview-flavored question with a given data structure. For the students in the latter half of the sequence, their solutions must be recursive and they may not use regular loops of any kind. Nothing like throwing a wrench into the specifications, right?

One of the most common mistakes that I see students make when programming in C++ is passing a pointer by value (with the intent of pointing it elsewhere for the calling routine) or not knowing when to pass some other data type by reference (like a class object).

Pass by reference is something that seems to fall by the wayside compared to their other learning goals; but it really shouldn’t! Passing a reference in C++ can make crafting recursive algorithms just a little more convenient. Not to mention, depending which standard of C++ you’re working with, you may not have move semantics or the default copy constructor (if you didn’t provide one) may come back to bite you. Personally, I think it’s worth it to learn a more modern standard of C++; but I digress.

Languages like Java or C don’t directly have pass-by-reference. In Java, you pass a reference by value. This is exactly like passing a pointer by value in C. There are workarounds, of course, but that’s beside the point for now.

My intent with this article is to provide clarification on how passing variables by value is different by reference; with a particular bias towards exploring passing pointers by reference as well. I also assume that you know what recursion is.

Passing by value

Chances are, you pass variables by value all the time. This is normal. We all do. When you pass a variable by value, a copy is made and that copy is given to the function you just called. Consider the following function:

void addFive(int x)
{
    x += 5;
    std::cout << "I added 5: " << x << "\n";
}

This function seems pretty straight forward. It adds 5 to the parameter named x and displays a triumphant message detailing our arithmetic prowess.

Here’s my main function:

int main()
{
    int x = 10;
 
    addFive(x);
 
    std::cout << "x = " << x << "\n";
 
    return 0;
}

What’s the output? Did my variable x in the main() function change its value after the addFive() function call?

I added 5: 15
x = 10

No, it doesn’t. It’s because we passed our variable by value. Our addFive() function received a copy of the variable that we passed into it when we called it in main(). We modified the copy, not the actual variable that lives in main().

If we want to modify the variable passed in to our function by the calling routine (aka, the x variable that lives in main()) we either need to return the new value, or change our function signature to take the variable by reference.

What is a reference?

I’m so glad you asked. A reference is an alias. Kind of like a pen name. Dr. Seuss is Theodor Seuss Geisel’s pen name, or his alias. Dr. Seuss and Geisel are the same person. When Dr. Seuss wrote a draft, that means that Geisel wrote the draft. This seems blatantly obvious, but this applies to C++ too!

In this context, a reference is a “pen name” for a variable. If the “pen name” does something or is modified, the original variable is changed too.

If you need a darker/more mystical example, think of a reference as a voodoo doll, and the variable that it references is the victim.

More concretely, you can think of a reference in C++ as a pointer that is already dereferenced (*myPtr = 43 == myRef = 43).

Let’s see it in action.

int main()
{
    int x = 10;
    std::cout << "x = " << x << "\n";
 
    int& penName = x; // Notice the &! This means it is a REFERENCE variable
    penName += 10;
    std::cout << "Now, x = " << x << "\n";
 
    return 0;
}

What do you think the output is?

x = 10
Now, x = 20

Notice that we modified the reference and that modification is reflected in the variable we referenced!

If you’re curious what our function signature would look like (from the previous section), it’d look like this:

void addFive(int& x) { . . . }

Passing pointers by reference

Let’s consider this recursive function:

int append(Node*& head, int data)
{
    if (!head) {
        head = new Node;
        head->data = data;
        head->next = nullptr;
        return 1;
    }
    return append(head->next, data);
}

This function appends a Node to the end of a linear linked list. Would this work if we passed the pointer by value? Not in its current state. We would run into the same problem as before with addFive(). We would be modifying what the copy points to, not what the original pointer points to.

Passing a pointer by reference in this case solves that problem and it also gives us some degree of convenience–it automates the “linking” of the list.

Let’s assume we call this function on an empty list (nullptr).

int main()
{
    Node* head = nullptr;
 
    append(head, 1);
 
    return 0;
}

That’s simple, our base case is triggered immediately and we point it at a newly allocated Node. Since we passed the Node pointer in by reference, it automatically assigns the original head pointer from main() to point at the new Node.

Okay, let’s do it again.

int main()
{
    Node* head = nullptr;
 
    append(head, 1);
    append(head, 2);
 
    return 0;
}

As we saw before, the first append() call creates the first Node and points our head pointer at it because our head variable in main() is initialized to nullptr.

In the second call, head is not nullptr, so we recurse, passing in head‘s next by reference. Since there’s only the one node, head‘s next pointer is nullptr and when we enter the second recursive call, our base case triggers, and we create a new Node and point our stack frame’s head to it.

Our current stack frame’s head pointer is our previous stack frame’s next pointer! Thus, it is automatically linked because we passed in our next pointer by reference!

See how easy it is? This makes for some concise, self-sufficient recursive functions. Try to practice recursion in C++ with this.

For posterity, here is the same function without pass-by-reference:

Node* appendByValue(Node* head, int data)
{
    if (!head) {
        head = new Node;
        head->data = data;
        head->next = nullptr;
        return head;
    }
 
    head->next = appendByValue(head->next, data);
    return head;
}

And here’s what it looks like in use:

int main()
{
    Node* head = nullptr;
 
    append(head, 1);
    head = appendByValue(head, 2);
 
    return 0;
}

Try to work your way through this after you’ve spent some time with the other version and trace the steps of the algorithm. Drawing diagrams certainly helps illustrate the differences and demonstrate what is happening.

4 thoughts on “Pass by Reference vs Pass by Value in C++

  1. While the explained content is all correct, the design idea of a reference pointer is very questionable. The append function here doesn’t do just one thing, but it effectively takes over the role of “object creation” and “data appending”, as such the function would have to be named like creator_or_append(Node*& node, int data).

    A truly append function would take an existing object and append a new value. That way you don’t have to get the newly created object back to the caller.

    void append(Node* node, int data) {
    if(node == nullptr) {
    // exception
    }
    if(node->next == nullptr) {
    node->next = new Node;
    node->data = data;
    node->next = nullptr;
    }
    else {
    append(node, data);
    }
    }

    Besides that, if I had the chance to influence the curriculum, I’d make sure to introduce students to raw pointers and manual memory management (new/delete) only in the advanced course and have the raw pointers only be used as non-owning raw pointers. Smart pointers are the way to go and it’s possible to not talk about these advanced topics until way later: https://www.youtube.com/watch?v=YnWhqhNdYyk

    Like

    1. You make a good point regarding the name of the function. My reasoning behind simply calling it append is because I consider “appending” to be basically synonymous with “push back” (like std::vector). My semantics could be wrong here.

      I completely agree with you regarding curriculum. I spent a long time with C++98 and manual memory management going through the lower division courses at my university; it hasn’t been until just recently that I’ve been exploring modern C++ on my own. Ownership with smart pointers is the best way to go about it.

      I wish we started with modern C++. I can understand in the grand scheme of the entire curriculum why we didn’t, but I would personally prefer that we did.

      Like

      1. What you actually want is to use member functions instead of free functions, but as long as you use free functions (for demonstration purposes), you need to keep the initial creation and future appending separated. So if you go with free functions, you’d also have to introduce a create and destroy function.

        I think your reasoning stumbles at the point, that you assume Node* itself is already an “object” while it actually is just a pointer. When you deal with a std::vector you don’t start with nothing, but you have a container object, which you can add new things to. Since the node structure itself is the container, you have to create the first node before you can operate on the container. Alternatively you could create a container class that then would operate purely on nodes, but then the container does have access to the first node at all times and the internal append function again doesn’t need a pointer reference.

        In the end it might have just been not the best example and there might be real reasons to use references of pointers. I personally just can’t think of one right now and whenever I’d see such a thing in a code base, I’d stop and question the design. Returning your modified pointer is after all not a bad design either, as the syntax is more readable, even though it might be slightly more verbose and requires more typing.

        I can understand in the grand scheme of the entire curriculum why we didn’t

        I don’t know your entire curriculum, but having gone through two C++ classes myself at uni and having been thought by a C++ committee member, I highly doubt that any curriculum really requires it. If you have the time, I highly recommend watching the talk I linked above, it’s excellent!

        Like

      2. Absolutely. I just did a narrow example of a free standing function for simplicity’s sake on the post. Other functions would be required for it to be decent on its own. I agree with your insight on how I fudged the semantics. I use the node very loosely as a rudimentary “container” just to reduce the amount of extraneous code people have to look at for my point. I agree with your reasoning on the semantics. It would make more sense to put the Node pointer in a List struct (if C) or even better in a class with methods (if C++).

        Admittedly, the audience of this article is mainly intended for students in the courses I work with to help clarify how to use pointers and pass-by-reference because it’s one of the topics I see students struggle with the most (since they use it in their code). Rather than shut them down entirely I thought it’d be a better exercise for this post to explore it and how to use it correctly if that’s the route they choose to take.

        In a real project, I would very much prefer references and objects; not pointers or pointers passed by reference.

        Edit: I’m planning on watching the video you linked when I get home from school 😃 Thanks for sharing it

        Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s