Week 2
References
string, int and double in memory.

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. The logical representation of a reference.

Week 2
Conversions & Const-ref

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
Week 2
Reference Parameters

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;
};
Visualization of pass-by-value.

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; }
Visualization of pass-by-reference.

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.

Week 2
String Value Parameters

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. Passing War and Peace to count_vowels.

Thus, using pass-by-value with string arguments is very inefficient.

Never pass class types, such as string and vector by value.

Week 2
String Reference Parameters

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