Lesson 27 - Passing by Value, Reference, and Const Reference

Passing by Value

Consider the following code. 


#include <iostream>

void fun(int a, int b){
a = 5;
b = 5;
}

int main(){
int number1 = 10;
int number2 = 20;
fun(number1, number2);
std::cout << number1 << " " << number2 << std::endl;
}


We have a function that takes in two variables and modifies them inside the body of the function. What would the output be as a result of line 13?


Don’t peek. Line 15 will print 10 20


You might have not even noticed this behavior, but even though on lines 5 and 6 assign both a and b to be equal to 5, the printed values will be 10 20 Why? The reason for this is due to what is happening internally when a function (with parameters) is called. 


Let’s read the code from top to bottom. On lines 4-7, we are implementing the function function(int a, int b). We are letting the compiler know that whenever the program contains the word function with a set of parentheses and values/variables as arguments, to actually call our function, the program will actually go back to the function declaration. In other words, when function(number1, number2) on line 13 is being executed, it’s almost like the program goes back to line 4. 


What happens when we actually get to line 4? A copy of all of the parameters is created. In other words, it’s almost as if there is an implicit line that says int a = 10 (value of number1) and int b = 20 (value of number2). 


What is the relevance of this information when programming? Let’s say we have a really large class named Drive (line 3). On line 18, we call the function do_something(Drive drive).

#include <iostream>

class Drive{
public:
Drive(int num_of_motors){...}

};

void do_something(Drive drive){
// Do something. Implementation not shown.

}

int main(){
Drive differential_drive(4);
do_something(differential_drive);

return 0;
}


When actually executing the function callback, like in the previous example, a COPY OF THE ARGUMENT  is made. The differential_drive object is created once again. This time, it’s somewhat problematic that C++ is making a copy of a large class. What we can instead do is pass through reference. 



Passing Through Reference

Let’s consider the following code. 


#include <iostream>

#include <iostream>

void fun(int &a, int &b){
a = 5;
b = 5;
}

int main(){
int number1 = 10;
int number2 = 20;
fun(number1, number2);
std::cout << number1 << " " << number2 << std::endl;
}


This time, Line 14 will print 5 5


The only difference between the example in the previous section and this example is a modification to the parameters of the function on Line 4. We have int &a and int &b as the parameters this time. This is known as passing by reference. Instead of passing by value (which would be int a, int b), on Line 4 what is going on is that we are passing a reference of number1 and number2.


Instead of a copy being made, like in passing by value, into the function, we are giving the function the memory addresses of number1 and number2. The variable a shares the same memory address as number1 and b shares the same memory address as number2. As a result of this, line 5, since a is essentially number1 (because they hold the same memory), modifying a will modify number1. Similarly, modifying b will modify number2


This time however, NO COPY OF number1 and number2 were made. The only issue is that modifying a and b will modify the variables that are passed in. Fortunately, there is a way to get around this through constness.


Note: Also, when calling the function fun the parameters must be a variable. For example, fun(10, 10); would not work. Nor will fun(number1, 10); work. The parameters must be a variable. 



Passing by Const-Reference

Consider the following code where the only modification from the past two examples is on Line 4.


#include <iostream>

#include <iostream>

void fun(const int &a, const int &b){
a = 5;
b = 5;
}

int main(){
int number1 = 10;
int number2 = 20;
fun(number1, number2);
std::cout << number1 << " " << number2 << std::endl;
}


This will actually produce an ERROR. WE CAN’T MODIFY ANYTHING THAT IS A CONST variable. However, the result of this is that no matter how hard we try, we won’t accidentally modify number1 and number2


When passing a large class (like in the Drive example), it is preferred to pass through const reference. For example, this is what the passing through const reference for the Drive class in the previous example would look like. However, if you are unable to pass through const reference since you are modifying the class, you will need to pass in through regular reference. Just be aware that you may mutate the class. 


#include <iostream>

class Drive{
public:
Drive(int num_of_motors){...}

};

void do_something(const Drive &drive){
// Do something. Implementation not shown.

}

int main(){
Drive differential_drive(4);
do_something(differential_drive);

return 0;
}



What Does This Mean For You?

Programming Guidelines

  1. When passing primitive data types (int, double, char, float, bool), pass by value. 

  2. When passing classes, it is typically better to pass by const reference. You will still be able to access the methods of the class.