Const Correctness #
Let’s start by looking at a “function object” with read only state. A function
object is a class which has the operator() defined. Let’s pick something with
sines and cosines:
f(x) = a*sin(x) + b*cos(x)
Translated to C++ it could be:
class Sinusoidal {
public:
Sinusoidal(double a, double b) : a(a), b(b) {}
double operator()(double x) {
return a * std::sin(x) + b * std::cos(x);
}
private:
double a;
double b;
};
What are the problems?
-
Inside
operator()we can accidentally change the value ofaandb. -
Even tough
operator()only readsaandb, we can’t do the following:void print_cref_v1(const SinusoidalV1 &sinusoidal) { std::cout << sinusoidal(42.0) << "\n"; // the offending line. }
The justification for const correctness is to avoid 1. and enable 2. The
reason why we want 2. is that it clearly states that passing sinusoidal to
print_cref_v1 does not change sinusoidal. We know this without looking at
the definition of print_cref_v1 and any function it might pass sinusoidal
to.
We only need to change the signature of operator() as follows:
double operator()(double x) const {
return a * std::sin(x) + b * std::cos(x);
}
There’s a nice subtlety to keep in mind: We can pass a temporary (or r-value reference) object to a function accepting const references; but not to a function accepting non-const references.
We demonstrate the difference with two functions print_{c,}ref.
void print_cref(const Sinusoidal &sinusoidal) {
std::cout << sinusoidal(42.0) << "\n";
}
void print_ref(Sinusoidal &sinusoidal) {
std::cout << sinusoidal(42.0) << "\n";
}
Note that the following compiles:
print_cref(Sinusoidal(1.0, 2.0));
while
print_ref(Sinusoidal(1.0, 2.0));
doesn’t. Instead we must do:
auto sinusoidal = Sinusoidal(1.0, 2.0);
print_ref(sinusoidal);
If you’re wondering how you could possibly remember this:
-
when accepting a const reference you’re saying: I’m not modifying the object, just looking. In particular you don’t particularly care where it is stored. You also don’t mind that the input will be destroyed as soon as the function returns.
-
when accepting a non-const reference you’re saying, that you want to modify the contents of the object. Therefore, if you could pass the temporary object to the function, whatever modifications you make to the input would be destroyed before the calling function can observe the change. Not useful.
Exceptions #
There algorithms that can be implemented much more efficiently if they have access to some internal scratch pad memory. Yet, on a high-level you don’t consider the method to be modifying the object (except of course the scratch pad, but that doesn’t really count).
There is a way out and it may be the correct choice, occasionally. Any member
defined as mutable can be modified from within const methods.
class Sinusoidal {
public:
Sinusoidal(double a, double b) : a(a), b(b), tmp_(1) {}
double operator()(double x) const {
tmp_[0] = a * b; // (1)
return a * std::sin(tmp_[0] * x) + b * std::cos(tmp_[0] * x);
}
private:
double a;
double b;
mutable std::vector<double> tmp_;
};
There’s one comment about the previous snippet:
- Imagine some situation where creating
tmp_everytime is too expensive, e.g. if one must allocate a small number of doubles only to do very litte work on them. To me the key is that all methods write totmp_before reading from it. Meaning no state is carried over from previous runs. Therefore, when dealing with single threaded code, this behaves like it does not modify its state.