Using C++ Classes
7.2 Pass by reference and const
When you write a function that takes a vector parameter, you should write it like this:
d o u b l e␣sum (␣c o n s t␣vector < double >&␣v␣)␣{
d o u b l e␣t o t a l␣=␣0 . 0 ; int␣n␣=␣v . s i z e ();
for␣( int␣i =0;␣i < n ;␣i ++)␣{
t o t a l␣+=␣v [ i ];
}
r e t u r n␣t o t a l ; }
Notice the strange const and & symbols that have crept in. The purpose of these additional symbols is to make it quicker to pass large objects around, as we will explain momentarily in Section 7.2.1.
I recommend that you learn the above function off by heart as a reminder of the syntax for
• Vectors
• for loops
• Pass by reference (the &)
• The const keyword.
7.2.1 Pass by reference
Consider the following short program. What do you think it prints out?
void␣printNextValue(␣int␣x␣)␣{
␣␣␣␣x␣=␣x␣+␣1;
␣␣␣␣cout␣<<␣"B:␣Value␣of␣x␣is␣"<<x<<"\n";
}
void␣main()␣{
␣␣␣␣int␣x␣=␣10;
␣␣␣␣cout␣<<␣"A:␣Value␣of␣x␣is␣"<<x<<"\n";
␣␣␣␣printNextValue(␣x␣);
␣␣␣␣cout␣<<␣"C:␣Value␣of␣x␣is␣"<<x<<"\n";
␣␣␣␣return␣0;
}
The answer is that it prints out:
A: Value of x is 10 B: Value of x is 11 C: Value of x is 10
The reason is that when you call a function ordinarily, a copy is made of your data and that copy is passed to the function. In this case a copy of the variable x is passed to printNextValue. This means that the original variable x remains unchanged when the copy is incremented.
On the other hand, the very similar program:
void␣printNextValue2(␣int&␣x␣)␣{
␣␣␣␣x␣=␣x␣+␣1;
␣␣␣␣cout␣<<␣"B:␣Value␣of␣x␣is␣"<<x<<"\n";
}
void␣main()␣{
␣␣␣␣int␣x␣=␣10;
␣␣␣␣cout␣<<␣"A:␣Value␣of␣x␣is␣"<<x<<"\n";
␣␣␣␣printNextValue2(␣x␣);
␣␣␣␣cout␣<<␣"C:␣Value␣of␣x␣is␣"<<x<<"\n";
␣␣␣␣return␣0;
}
prints out something different. It prints out:
A: Value of x is 10 B: Value of x is 11 C: Value of x is 11
The only important difference is the & symbol used in the definition of print-NextValue2. In this case the parameter x is passed by reference. This means that the variable x in the function main is actually modified by printNext-Value2. The function is not passed a copy of the variable, but instead is asked to modify the variable itself.
We say that the function printNextValue which uses pass by value has no side effects since the function simply performs the computation you expect.
On the other hand, printNextValue2 has the side effect of unexpectedly changing the value of x.
As we will see in the next section, there are times when you might actually want a function to change the values of the data that you pass as parameter.
When this happens, it is good practice to make sure this is obvious from the name of the function. If printNextValue2 was instead called increment-AndPrint it would not be such a confusing function. Its name would make its behaviour clear. In general, programmers assume that a function won’t change the parameters passed to it unless the documentation makes that very clear.
As a result, just as in medicine, unwanted side effects are usually considered a bad thing.
This would seem to suggest that you should rarely use pass by reference.
However, when you pass large objects around, passing data by reference can be much quicker than passing a copy by value. This is because creating a copy of a large object can take a lot of time. With pass by reference there is no need for any copying. Pass by reference works by telling the function you are calling whereabouts in memory your data can be found. Your data is read, and potentially modified, in situ.
Tip: When to use pass by reference (&)
When passing objects, pass them by reference as your default choice.
When passing the primitive data types double, int, and bool, pass by value is perhaps marginally quicker, so for these data types it is conventional to use pass by value.
7.2.2 The const keyword
We seem to be caught between two alternatives: Pass by reference is quick but potentially confusing; pass by value is slow but should not cause confusion.
There are two possible solutions.
• Use pass by reference but take care not to actually modify the
parame-ters that are passed to your function. This may seem a facetious solution to the problem, but it is a very practical approach.
• Use pass by reference and ask the compiler to prove that the function doesn’t modify the parameters passed in. You can do this by using the const keyword.
When you pass a parameter as a const parameter, this means that the function you are calling is not allowed to change the value of the parameter.
For example, if you change the code in our example so it now reads:
void␣printNextValue(␣const␣int&␣x␣)␣{
␣␣␣␣x␣=␣x␣+␣1;
␣␣␣␣cout␣<<␣"B:␣Value␣of␣x␣is␣"<<x<<"\n";
}
you will no longer be able to compile the code. This is because the function attempts to modify the variable x contradicting the use of the const keyword.
You can use the const keyword on variables, too. We’ve seen it used on global variables, but you can use it on local variables. Whenever it is used, it means that the value of the variable must not change.
Tip: Use const consistently or don’t use it at all
As we have discussed, using the const keyword means the compiler will check your functions to make sure they don’t have any side effects.
This has some advantages, but it has the obvious downside that you need to type const! If you choose to use the const keyword you will quickly discover that you must use the const keyword consistently throughout your code in order to avoid compilation errors. It turns out that this will mean typing const extremely often.
You need to decide whether you want to use const in your code or not.
In this book we will demonstrate how to write code using the const keyword and so will use const a lot.
If you are working on an existing C++ project, a decision will have been made on whether or not to use const in that project. In this case you should fall in with this decision.
If you are writing your own software project, perhaps for a student disser-tation, I personally would recommend not using the const keyword. I believe that you will waste time trying to understand the compiler errors raised by const that could be better spent writing unit tests. However, this view is controversial and many projects do choose to use the const keyword.
7.2.3 Pass by reference without const
We have seen that pass by reference without the const keyword can lead to unwanted side effects. Nevertheless, there are times when you actually want a function to change its parameters.
One example is that C++ does not allow you to return multiple values.
You can use pass by reference to get round this.
As an example, suppose that we want to write a function to convert the polar coordinates (r, θ) into the Cartesian coordinates (x, y). We cannot write a function that returns two doubles, so instead we write a function that doesn’t return anything, but modifies two variables x and y that are passed in by reference.
v o i d␣p o l a r T o C a r t e s i a n (␣d o u b l e␣r ,␣d o u b l e␣theta , d o u b l e &␣x ,␣d o u b l e &␣y␣)␣{ x␣=␣r * cos ( t h e t a );
y␣=␣r * sin ( t h e t a );
}
Here is an example of how to use this function:
s t a t i c␣v o i d␣t e s t P o l a r T o C a r t e s i a n ()␣{
d o u b l e␣r␣=␣2 . 0 ; d o u b l e␣t h e t a␣=␣PI /2;
d o u b l e␣x =0.0 , y = 0 . 0 ;
p o l a r T o C a r t e s i a n ( r , theta , x , y );
A S S E R T _ A P P R O X _ E Q U A L (␣x , 0 . 0 , 0 . 0 0 1␣);
A S S E R T _ A P P R O X _ E Q U A L (␣y , 2 . 0 , 0 . 0 0 1␣);
}
As we shall see in the next chapter, user defined types provide an alterna-tive means of returning multiple values.