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:
- The School copy constructor gets called to initialize the parameter s from harvard.
- The parameter s gets destroyed when validateSchool returns.
However, this is not the whole story. Let me show you what else occurs:
- Since School has two string types, the string's constructors are also called.
- Since School inherits from Building, the base class constructor also gets called.
- The base class also contains two string types whose constructors are also called.
- 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++.