This post is part of a series of tutorials on C++

In C++, some care must be taken if one wants to pass around objects as parameters and return types to functions. The default method for passing a parameter to a function is to pass-by-value. As a result, even a simple function call such as

int foo(int param) {
  param *= 2;
  return param;
}

In actuality requests that one makes a copy of some parameter whenever foo() is called. For primitive data types, this is handy because it prevents you from accidentally changing a value when it is out of scope. For example:

int main() {
  int bar = 10;
  int foobar = foo(bar);
  //foobar == 20, but bar == 10
}

Protects the validity of the variable bar being passed into the function foo(). The expense is that a copy of bar must be made when param is created. But, for a primitive type, this is (maybe) not so expensive, unless foo() is called repeatedly. When using an object, this could add up, particularly when the object has a large number of data members that all need to be copied.

To get around this, we can take advantage of references, which when passed into functions do not make a copy but instead allow you to refer to the same address in memory without requiring the copy. Of course, this could be particularly disributive:

int foo(int& param) {   //pass-by-reference
  param *= 2;           //we are modifying the variable OUTSIDE of scope
  return param;
}

The compound multiple-and-assignment stores the produce of param*2 back into param, which is really just another name for whatever variable we were already working with. Thus:

int main() {
  int bar = 10;
  int foobar = foo(bar);
  //foobar == 20, but bar == 20 too!
}

Languages like Java make the default behavior that everything is passed by reference and that copies need to be explicit. This, of course, creates the above problem for the unwary. In C++, one can fix this by being explicit on whether or not the reference should be treated as read-only, using const:

int foo(const int& param) {   //pass-by-const-reference
  param *= 2;                 //compiler error!
  return param;
}

Even though the above is a little bit more cumbersome, it also forces the programmer to think carefully about how variables are used internally. Am I just reading the value, or do I want to update it too? If you’re just reading, you are forced to code foo() differently. For example:

int foo(const int& param) {   //pass-by-const-reference
  return param*2.0;           //just read the value and compute the multiply, no store
}

Which is likely what we intended for foo() in the first place. Getting comfortable with const references will save you a lot of grief. It will also force you to think carefully about as to if your member functions (and, in particular, operators that you overload) are read-only or if they are meant to change parameters too.

And, for complex types, this distinction becomes even more important. Using references helps with memory usage (e.g., preventing unnecessary copying), but it also helps prevent you from accidentally modifying member types. For example:

class Bar {
  public:
    float param;
};

int foo(const Bar& b) {     //pass-by-const-reference
  b.param *= 2.0            //storing in the parameter, not allowed because of const
  return b.param*2.0;       //just read the value and compute the multiply, no store
}

As we’ll see in many of the upcoming tutorials, we’ll be using const references and references to pass around types. For C++ objects, there is rarely a good reason to pass an instance of a class by value, so as a rule of thumb if you ever see yourself accidentally doing it, you may have intended to use a const reference instead!



Back to C++ Tutorials