In C++, both library types, like string, and the built-in primitive types, like int and double, are called value types. In C++ such variables are "boxes" that contain data.
C++ also has several derived types:
- pointers, which contain the address of a variable,
- arrays, which contain a sequence of variables
- references, which provide an alias or alternate name for an existing variable
A reference name is an alternate name or alias for an existing object. Here’s an example of a variable n and its alias r. You create a reference by putting an ampersand (&) after the type name. The type of r is usually pronounced "int-ref".
int n = 3;
int& r = n; // r is now an alias for n
r = 42; // n is also now 42
Here, r is simply an alternate name for n. It is not a new variable. Under the hood, the compiler often uses pointers to implement references (although that's not required). Even if you understand how pointers work, however, you should try not to get the two concepts confused.
Unlike value-type variables, references have no implicit conversions. For instance, the following compiles and runs, because even though a is type int and b is type double, the compiler will implicitly create a temporary int value to "stand in" for b.
int a = 42;
double b = a; // implicitly double b = int(a)
The following code, however, will not compile, because x is an int, but rx is a reference to a double. If rx were a double, (as in the previous example), instead of a double& then x would be promoted and stored in rx.
int x = 3;
double& rx = x; // ILLEGAL. x is not a double
Constant References
While regular references must refer to an lvalue of exactly the same type, a constant reference may refer to a literal or temporary value. Here are some examples:
int& lit = 3; // ILLEGAL
const int& lit2 = 3; // OK, const-ref
string& str = "OK"; // ILLEGAL
const string& str2 = "OK"; // OK
When you pass a variable to a function, the function receives a copy of the calling value or argument. Assigning to a parameter variable changes the parameter but has no effect on the argument. Consider this program, along with a function which attempts set a variable to zero:
void toZero(int n) { n = 0; }
int main()
{
int x = 42;
toZero(x);
cout << x << endl;
};
If you call the procedure the parameter variable named n is initialized with a copy of the value is stored in x (42 in this case). Making a copy of arguments when calling a function, is known as pass by value or call by value, and the parameter n is known as a value parameter.
The assignment statement n = 0; inside the function sets the parameter variable n to 0 but leaves the variable x unchanged in the main function.
Pass by Reference
If you want to change the value of the calling argument, you can change the parameter from a value parameter into a reference parameter by adding an ampersand between the type and the name in the function header, like this:
void toZero(int& n) { n = 0; }
Unlike value parameters, reference parameters are not copied. Instead, the function treats n as a reference to the original variable, which means that the memory used for that variable is shared between the function and its caller.
If you trace through the program by clicking the link, you'll see that this time, the variable x in main is set to 0, just as you intended.
Imagine you want to write a function named count_vowels(), which counts the number of vowels in a string. Here's a first attempt:
int count_vowels(string str) {
int vowels = 0;
for (char c : str) {
switch (c) {
case 'a': case 'A': case 'e': case 'E': case 'i':
case 'I': case 'o': case 'O': case 'u': case 'U':
vowels++;
}
}
return vowels;
}
The code in this function is correct, readable, and quite efficient. However, it has one flaw. Imagine calling the function with a long string, say the text of War and Peace. Because the parameter variable str is a value parameter, your code will make a copy of the whole text of the book and store that in str.
Thus, using pass-by-value with string arguments is very inefficient.
Never pass class types, such as string and vector by value.
Since reference parameters don't make a copy of the argument, they are much more efficient when passing a class-type argument such as string or vector. What if you were to change the heading of count_vowels like this. Would that work?
int count_vowels(string& str)
Well, yes and no!
- Because the parameter str is now a reference, there is no copy made, so it is much more efficient.
-
However, because it is a reference, you can now only call the count_vowels function with an lvalue. You could no longer write:
count_vowels("hello");
. Your function is much less usable. -
Finally, since str is a reference, there is nothing to prevent the
count_vowels
function from inadvertently modifying the parameter, and, thus by extension, the argument. The function is not as safe as it could be.
The solution is simple, however. Whenever you pass a string as an argument to a function, use const string& for the parameter if the function will not modify the calling argument, and string& if it will.
Here is the improved header for count_vowels, which is correct, efficient and safe.
int count_vowels(const string& str)
If the string should be modified use a regular reference. If the string should not be modified, use a const reference as your parameter type.
You can add these C++11 type alias declarations to your programs to make this easier if you like:
using stringIn = const string&; // input string not modified
using stringRef = string& // output string, modified