Const Correctness
Const correctness means marking all items you can const
to prevent unwanted mutation. Let’s say you want to grab a few options
from a settings
map that you’ve created.
Let’s say you want the time to live value and the created at from a map.
Does this compile?
void setTimeToLive(int ttl) {/* implementation here */}
void setCreatedAt(int createdAt) {/* implementation here */}
void getOptions(const std::map<const char*, int> &m) noexcept {
const auto ttl = m["ttl"];
const auto createdAt = m["createdAt"];
setTimeToLive(ttl);
setCreatedAt(createdAt);
}
Nope: since map[]
will insert in the case that it
doesn’t find a matching key, this doesn’t compile. We can’t insert into
a map marked const.
Let’s say we didn’t mark the map as const
:
void setTimeToLive(int ttl) {/* implementation here */}
void setCreatedAt(int createdAt) {/* implementation here */}
void getOptions(std::map<const char*, int> &m) noexcept {
const auto ttl = m["tttl"]; // oops, typo
const auto createdAt = m["createdAt"];
setTimeToLive(ttl);
setCreatedAt(createdAt);
}
This compiles now, but oh no, what’s the value of ttl?
0
. When we access a map’s key that doesn’t exist, we get
some default value. In our case, a value of 0.
So we’re at a crossroads. We want our code to compile, be correct,
and still allow the map to be const
.
Let’s let that happen:
void setTimeToLive(int ttl) {/* implementation here */}
void setCreatedAt(int createdAt) {/* implementation here */}
void getOptions(const std::map<const char*, int> &m) {
const auto ttl = m.at("tttl"); // oops, typo
const auto createdAt = m.at("createdAt");
setTimeToLive(ttl);
setCreatedAt(createdAt);
}
We replace map::[]
with map::at
.
map::at
does a checked get in the map. If it doesn’t find
the key, it throws an exception.
We remove our noexcept
from the function because this
function can throw and we move on with our lives.
Const correctness saves lives.