Effective C++ item 3: Use const whenever possible

1
2
3
4
5
6
7
8
9
class Rational
{
public:
....

const Rational operator*(const Rational& lhs, const Rational& rhs);

....
};

If you miss the first const, one simple typo can make it wrong:

1
2
Rational a, b, c;
if (a * b = c)

In the code above, I know you want to type == not =. But compiler will happily generate code for you, and maybe you have to spend hours to debug. All you need to do is use const to prevent unwanted behavior.

Let’s see an example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class TextBlock
{
public:
....

const char& operator[](std::size_t position) const
{
return m_text[position];
}
char& operator[](std::size_t position)
{
return m_text[position];
}
private:
std::string m_text;
};

And then to illustrate how const member function works:

1
2
3
4
5
6
TextBlock nonConstObj("Hello");
const TextBlock constObj("Hello");
std::cout << nonConstObj[0]; // call const member function
std::cout << constObj[0]; // call const member function
nonConstObj[0] = 'J'; // fine
constObj[0] = 'J'; // error!

Typically corresponding const and non-const member functions are very similar, to avoid code duplication, we make non-const member function call const member function with certain cast:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class TextBlock
{
public:
....

const char& operator[](std::size_t position) const
{
...
return m_text[position];
}
char& operator[](std::size_t position)
{
return const_cast<char&>(static_cast<const TextBlock&>(*this)[position]);
}
private:
std::string m_text;
};

As you can see, the code has two casts, not one. We want the non-const operator[] to call the const one, but if, inside the non-const operator[], we just call operator[], we’ll recursively call ourselves. So we cast *this from TextBlock& to const TextBlock&. The static_cast is to remove the const from the const operator[]‘s return value.

For example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class TextBlock
{
public:
....

const char& operator[](std::size_t position) const
{
...
return m_text[position];
}
char& operator[](std::size_t position)
{
return const_cast<char&>(static_cast<const TextBlock&>(*this)[position]);
}
std::size_t length() const
{
m_textLength = m_text.size();
return m_textLength;
}
private:
std::string m_text;
mutable std::size_t m_textLength;
};

Here, m_textLength can be changed in const member function.

Reference:
“Effective C++” Third Edition by Scott Meyers.