Passing By Reference C++ Array Assignment

Function pass by value vs. pass by reference

I will call what you are passing in a to a function the actual parameters, and where you receive them, the parameters in the function, the formal parameters. They are also called actual and formal arguments.

When passing parameters, what it is called and what happens can be confusing. It is less essential that you call it the "correct" thing than you know exactly what is happening. It is critical to have a good mental model, a valid memory picture of the process.

Recall that when you call a function, a chunk of memory called an activation record is allocated. Critical to the discussion here is that this memory holds the formal parameter values and function local variables.

By definition, pass by value means you are making a copy in memory of the actual parameter's value that is passed in, a copy of the contents of the actual parameter. Use pass by value when when you are only "using" the parameter for some computation, not changing it for the client program.

In pass by reference (also called pass by address), a copy of the address of the actual parameter is stored. Use pass by reference when you are changing the parameter passed in by the client program.

Consider a swapping function to demonstrate pass by value vs. pass by reference. This function, which swaps ints, cannot be done in Java.

First, we show the memory picture for swapThemByVal. The activation record holds the memory for the two parameters, num1 and num2, and the local variable, temp. A copy of the values from main, in the contents of i and j, are copied. All the manipulation is done in the activation record. The contents of memory of   i   and   j   don't change. The contents of memory in the function's activation record changes, but when the function terminates, the memory is released and the changes are lost.

Contrast this with passing by reference. The addresses of   i   and   j   are passed (noted by the arrows) by reference. The compiler knows they are references so when the parameters are referred to in the function, the compiler dereferences num1 and num2 automatically so   i   and   j   of main's memory are changed. After the assignments: This is the essence of pass by value vs. pass by reference. It doesn't matter if the parameters are primitive types, arrays, or objects, either a copy is made or an address is stored. As noted elsewhere, when objects are copied, the copy constructor is called to do the copying.

Typically if you aren't going to change a variable, you use pass by value. But if you are passing something in that uses a lot of memory, i.e., passing an object or passing an array, even if you aren't changing it, you use what I like to call fake pass by value.

For efficiency, you pass by reference so only the address is passed, but you put a const in front of it. This casts it to a constant for use in the function. Note that if this function passes to some other function, it is now constant object or array. For example:

There is one more way to pass variables to functions, and that is by address. Passing an argument by address involves passing the address of the argument variable rather than the argument variable itself. Because the argument is an address, the function parameter must be a pointer. The function can then dereference the pointer to access or change the value being pointed to.

Here is an example of a function that takes a parameter passed by address:

The above snippet prints:

value = 5 value = 6

As you can see, the function foo() changed the value of the argument (variable value) through pointer parameter ptr.

Pass by address is typically used with pointers, which most often are used to point to built-in arrays. For example, the following function will print all the values in an array:

Here is an example program that calls this function:

This program prints the following:

6 5 4 3 2 1

Remember that fixed arrays decay into pointers when passed to a function, so we have to pass the length as a separate parameter.

It is always a good idea to ensure parameters passed by address are not null pointers before dereferencing them. Dereferencing a null pointer will typically cause the program to crash. Here is our printArray() function with a null pointer check:

Passing by const address

Because printArray() doesn’t modify any of its arguments, it’s good form to make the array parameter const:

This allows us to tell at a glance that printArray() won’t modify the array argument passed in, and will ensure we don’t do so by accident.

Addresses are actually passed by value

When you pass a pointer to a function by address, the pointer’s value (the address it points to) is copied from the argument to the function’s parameter. In other words, it’s passed by value! If you change the function parameter’s value, you are only changing a copy. Consequently, the original pointer argument will not be changed.

Here’s a sample program that illustrates this.

tempPtr receives a copy of the address that ptr is holding. Even though we change tempPtr to point at something else (nullptr), this does not change the value that ptr points to. Consequently, this program prints:

55

Note that even though the address itself is passed by value, you can still dereference that address to change the argument’s value. This is a common point of confusion, so let’s clarify:

  • When passing an argument by address, the function parameter variable receives a copy of the address from the argument. At this point, the function parameter and the argument both point to the same value.
  • If the function parameter is then dereferenced to change the value being pointed to, that will impact the value the argument is pointing to, since both the function parameter and argument are pointing to the same value!
  • If the function parameter is assigned a different address, that will not impact the argument, since the function parameter is a copy, and changing the copy won’t impact the original. After changing the function parameter’s address, the function parameter and argument will point to different values, so dereferencing the parameter and changing the value will no longer affect the value pointed to by the argument.

The following program illustrates the point:

This prints:

56

Passing addresses by reference

The next logical question is, “What if we want to change the address an argument points to from within the function?”. Turns out, this is surprisingly easy. You can simply pass the address by reference. The syntax for doing a reference to a pointer is a little strange (and easy to get backwards). However, if you do get it backwards, the compiler will give you an error.

The following program illustrates using a reference to a pointer:

When we run the program again with this version of the function, we get:

5 ptr is null

Which shows that calling setToNull() did indeed change the value of ptr from &five to nullptr!

There is only pass by value

Now that you understand the basic differences between passing by reference, address, and value, let’s get reductionist for a moment. 🙂

In the lesson on references, we briefly mentioned that references are typically implemented by the compiler as pointers. This means that behind the scenes, pass by reference is essentially just a pass by address (with access to the reference doing an implicit dereference).

And just above, we showed that pass by address is actually just passing an address by value!

Therefore, we can conclude that C++ really passes everything by value! The properties of pass by address (and reference) comes solely from the fact that we can dereference the passed address to change the argument, which we can not do with a normal value parameter!

Pros and cons of pass by address

Advantages of passing by address:

  • Pass by address allows a function to change the value of the argument, which is sometimes useful. Otherwise, const can be used to guarantee the function won’t change the argument. (However, if you want to do this with a non-pointer, you should use pass by reference instead).
  • Because a copy of the argument is not made, it is fast, even when used with large structs or classes.
  • We can return multiple values from a function via out parameters.

Disadvantages of passing by address:

  • Because literals and expressions do not have addresses, pointer arguments must be normal variables.
  • All values must be checked to see whether they are null. Trying to dereference a null value will result in a crash. It is easy to forget to do this.
  • Because dereferencing a pointer is slower than accessing a value directly, accessing arguments passed by address is slower than accessing arguments passed by value.

When to use pass by address:

  • When passing built-in arrays (if you’re okay with the fact that they’ll decay into a pointer).
  • When passing a pointer and nullptr is a valid argument logically.

When not to use pass by address:

  • When passing a pointer and nullptr is not a valid argument logically (use pass by reference and dereference the pointer argument).
  • When passing structs or classes (use pass by reference).
  • When passing fundamental types (use pass by value).

As you can see, pass by address and pass by reference have almost identical advantages and disadvantages. Because pass by reference is generally safer than pass by address, pass by reference should be preferred in most cases.

Rule: Prefer pass by reference to pass by address whenever applicable.

#include <iostream>

 

voidfoo(int*ptr)

{

    *ptr=6;

}

 

intmain()

{

    intvalue=5;

 

    std::cout<<"value = "<<value<<'\n';

    foo(&value);

    std::cout<<"value = "<<value<<'\n';

    return0;

}

voidprintArray(int*array,intlength)

{

    for(intindex=0;index<length;++index)

        std::cout<<array[index]<<' ';

}

intmain()

{

    intarray[6]={6,5,4,3,2,1};// remember, arrays decay into pointers

    printArray(array,6);// so array evaluates to a pointer to the first element of the array here, no & needed

}

voidprintArray(int*array,intlength)

{

    // if user passed in a null pointer for array, bail out early!

    if(!array)

        return;

 

    for(intindex=0;index<length;++index)

        cout<<array[index]<<' ';

}

 

intmain()

{

    intarray[6]={6,5,4,3,2,1};

    printArray(array,6);

}

voidprintArray(constint*array,intlength)

{

    // if user passed in a null pointer for array, bail out early!

    if(!array)

        return;

 

    for(intindex=0;index<length;++index)

        std::cout<<array[index]<<' ';

}

 

intmain()

{

    intarray[6]={6,5,4,3,2,1};

    printArray(array,6);

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

#include <iostream>

 

voidsetToNull(int*tempPtr)

{

    // we're making tempPtr point at something else, not changing the value that tempPtr points to.

    tempPtr=nullptr;// use 0 instead if not C++11

}

 

intmain()

{

    // First we set ptr to the address of five, which means *ptr = 5

    intfive=5;

    int*ptr=&five;

    // This will print 5

    std::cout<<*ptr;

 

    // tempPtr will receive a copy of ptr

    setToNull(ptr);

 

    // ptr is still set to the address of five!

 

    // This will print 5

    if(ptr)

        std::cout<<*ptr;

    else

        std::cout<<" ptr is null";

 

    return0;

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

#include <iostream>

 

voidsetToSix(int*tempPtr)

{

    *tempPtr=6;// we're changing the value that tempPtr (and ptr) points to

}

 

intmain()

{

    // First we set ptr to the address of five, which means *ptr = 5

    intfive=5;

    int*ptr=&five;

    // This will print 5

    std::cout<<*ptr;

 

    // tempPtr will receive a copy of ptr

    setToSix(ptr);

 

    // tempPtr changed the value being pointed to to 6, so ptr is now pointing to the value 6

 

    // This will print 6

    if(ptr)

        std::cout<<*ptr;

    else

        std::cout<<" ptr is null";

 

    return0;

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

#include <iostream>

 

// tempPtr is now a reference to a pointer, so any changes made to tempPtr will change the argument as well!

voidsetToNull(int*&tempPtr)

{

    tempPtr=nullptr;// use 0 instead if not C++11

}

 

intmain()

{

    // First we set ptr to the address of five, which means *ptr = 5

    intfive=5;

    int*ptr=&five;

    // This will print 5

    std::cout<<*ptr;

 

    // tempPtr is set as a reference to ptr

    setToNull(ptr);

 

    // ptr has now been changed to nullptr!

 

    if(ptr)

        std::cout<<*ptr;

    else

        std::cout<<" ptr is null";

 

    return0;

}

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *