https://pomf2.lain.la/f/819ag2cg.pngThe linked image has formatting as intended originally; for the post text I had to use alternative characters to avoid triggering the site formatting, which REALLY needs a proper pre-formatted code feature.Where most other languages have rules about passing primitive types by value and objects by reference, C/C++ builds subtle control of that into its language. This is no simple quirk; passing by value is also known as
copying, and that comes with a real performance cost, such that learning a language which controls it
is the nature of achieving good performance in and of itself.
Consider a simple and fairly useless class representing a square with a width and a height, entirely public to ignore encapsulation for now. We store the width and height, and we provide an area calculation and a function to scale its size:
class Square
{
public:
double width;
double height;
double area()
{
return width ✱ height;
}
void scale(double factor)
{
width ✱= factor;
height ✱= factor;
}
};
This works at least, but neither as optimally nor with as much freedom as it could have.
The freedom point is good to address first; consider how that
area function. Despite only
reading the class variables without changing them, the entire function will be treated as if it
can conceivably change the
Square object on which it is called. Due to that, the
area function will be inaccessible given a
const Square object, even though it makes no changes.
The
area function should be specified like this:
double area() const
{
return width ✱ height;
}
Shoving
const in the function header makes the object fields read-only within the function—in return for being allowed to call the function on a
const version of the object. Use of
const is both a means of object protection and also a precursor to compiler optimisations such as avoiding copying and reloads.
Now to be more optimal, consider the
scale function. It modifies the object and thus cannot be
const, but this is about the scale factor parameter. Calling that function will supply it with a
copy of the factor, which can be appropriate if it genuinely needs a temporary copy it can modify without affecting the calling code, but clearly it doesn't change the factor. In one sense, it would be better to accept a reference instead:
void scale(double & factor)
{
width ✱= factor;
height ✱= factor;
}
This bypasses the overhead of copying, although now that function cannot be used with a
const double, because it could conceivably change the value (even though in practice it doesn't). This applies to calls with literal number as the factor, such as scale(1.5)—that 1.5 is itself a
const double.
The
scale function should be specified like this:
void scale(const double & factor)
{
width ✱= factor;
height ✱= factor;
}
Introducing that guarantee now allows literal numbers and any named values (
const or otherwise), while also avoiding copying anything.
Even though this example involves only a
double weighing in at 8 bytes, that is nonetheless both how to talk to the C/C++ compiler and also how to avoid unnecessary copying. It is not an early optimisation mistake; it is a basic part of the language which should be in continual use, it just so happens that the language and the optimisation are one and the same!