Harold Serrano

View Original

C++ tip 13: Prefer pass-by-reference-to-const to pass-by-value

When working with C++ you may be tempted to pass objects to and from functions by passing-by-value. This is OK when you are working with built-in types, but it is a bad idea when using your own types.

For example, consider the base and derived classes shown below:

class Building{
public:
Building();

private:
std::string name;
std::string address;
};

class School:public Building{
public:
School();

private:
std::string schoolName;
std::string schoolAddress;
};

Now consider a simple function validateSchool() that takes in as a parameter a School type as passing-by-value:

bool validateStudent(School s);

What do think will happens when you do the following:

School harvard;
bool isSchool=validateSchool(harvard);

You know the following occurs:

  1. The School copy constructor gets called to initialize the parameter s from harvard.
  2. The parameter s gets destroyed when validateSchool returns.

However, this is not the whole story. Let me show you what else occurs:

  1. Since School has two string types, the string's constructors are also called.
  2. Since School inherits from Building, the base class constructor also gets called.
  3. The base class also contains two string types whose constructors are also called.
  4. When the School type gets destroyed, the base class is also destroyed and the four string types are also destroyed.

What this means is that passing a School by value leads to calling:

  • 6 construstors and 6 destructors!!!

Is there a way to bypass all of these constructors/destructors calls? Yes, it is to pass-by-reference-to-const:

bool validateStudent(const Student& s);

Passing by reference-to-const is much more efficient since no constructors/destructors are called.

So, here is your tip from the Effective C++ book:

Prefer pass-by-reference-to-const to pass-by-value

The Slicing Problem

Passing by reference-to-const also fixes the slicing problem. This is when you pass a derived type to a function and only the base type portion of the derived class survives.

For example, consider the following:

class Building{
public:
Building();
virtual void payBills(){};

private:
std::string name;
std::string address;
};

class School:public Building{
public:
School();
void payBills();

private:
std::string schoolName;
std::string schoolAddress;
};

The fact that payBills() in the base class is virtual means that School has implemented its own way how to pay bills.

Now, consider the function below that takes in a pass-by-value parameter:

void payYourBills(Building b){
b.payBills();
}

What do you think happens when you do the following:

School s;
payYourBills(s);

The parameter b will be constructed as a Building type (it is passed by value) and all of the information about the School type will be sliced off. Inside payYourBills(), b will always act like an object of class Building, regardless of the type of object passed to the function.

To fix this slicing issue use pass-by-reference-to-const instead:

void payYourBills(const Building& b);

Scott Meyers suggests in his book Effective C++ to prefer pass-by-reference-to-const over pass-by-value. This is more efficient and it avoids the slicing problem.

He also suggests that pass-by-reference-to-const should be used for your own data types, not for built-in types, STL iterator and function object types. For them, pass-by-value is usually appropriate.

Hope this tip helps.

Sign up to my newsletter to get tips on Game Engine Development and C++.