By Carlo Wood |
Consider an arbitrary type TYPE.
TYPE x;
The variable x is not const when it is possible to change its value, for instance by assignment:
x = ...;
When the string represented above by TYPE does not contain a reference or pointer, then you can make it a constant by putting a const in front of it:
const TYPE x = ...;
You can declare a constant of any type, by putting const behind it:
TYPE const x = ...;
Notes
template<typename TYPE> void foobar(const TYPE&);
is equivalent with
template<typename TYPE> void foobar(TYPE const&);
even when TYPE is a pointer.
Another way of getting (to do with) const types is when you pass a reference or pointer type to a function. Consider:
TYPE x;
(We know that x is not const (and thus also not a reference): it isn't initialized)
foobar(x); // Call function foobar with parameter x.
The prototype for foobar can have any of the following footprints:
void foobar(TYPE); // Pass by value void foobar(TYPE&); // Pass by reference void foobar(TYPE const&); // Pass by const reference
Note that I put the const to the right of TYPE because we don't know if TYPE (this is not a template parameter, but rather for instance a literal char*) is a pointer or not!
The last prototype promises not to change x, and hence it would be ok to pass a constant by reference to this function:
TYPE const x = { ... }; foobar(x); // Ok for prototype 1 and 3.
When sizeof(TYPE) is larger than the builtin types, people often prefer to pass the object as a const reference (const&), instead of making a copy of the whole object (as prototype 1 would do).
When dealing with pointers, things are similar:
void foobar(TYPE); // Pass by value void foobar(TYPE*); // Pass by pointer void foobar(TYPE const*); // Pass by pointer-to-const
A peculiar result of using a reference instead of pointer is (apart from having to type less * and & characters in your source code) that you are allowed to pass temporaries to functions without copying them (as a 'pass by value' would do). After all, you are not allowed to take the pointer to a temporary and therefore can not use the third prototype (Pass by pointer-to-const) even while the prototype itself promises not to change the temporary. It is possible to pass a temporary to a function as a reference-to-const though:
void foobar(TYPE const&); foobar(TYPE()); // Allowed.
The confusion starts when TYPE itself is a pointer or reference and in particular when it already contains const qualifiers.
For instance:
int x1; // TYPE is "int" const int x2 = 3; // x2 is a constant int* p1; // TYPE is "int*" const int* p2; // p2 is NOT a constant
Huh? No initialisation needed? No, because the variable p2 is not a constant: It only points to constant data!
If you want to make p2 itself constant then you'll have to write:
int* const p2 = ...;
This is the reason that it is better to put qualifiers always to the right of the TYPE, to avoid confusion:
int x1; int const x2 = 3; int* p1; int* const p2 = &x1;
Note that now we have to initialize p2.
But how to declare a pointer constant that is initialized with &x2 (a pointer to an int const)?
int const* p3; // pointer to `the type of x2' int const* const p4 = &x2; // pointer constant to `the type of x2'
This is very logical, when you realize that the qualifiers work on everything on the left of them, as do * and & in types.
Therefore, put the * for a pointer and the & for a reference, directly against the type on the left of it. That is better because it makes clear that it isn't an operator* (or operator&), but that it is part of the type.
For instance, int const&* const** means «A pointer to int const&* const*»; the * in types operates on everything on the left of it.
Important: When you look at a type that contains a * (or a &) always realize that the * (&) works on everything on the left. This will avoid confusion when people write:
const Foo&
Instead of
Foo const&
In fact, many gurus use the first version: they put a const to the left of types that are not a reference or pointer. This has a historical reason, the style was born in a very early stage. Bjarne Stroustrup wrote me:
I don't remember any deep thoughts or involved discussions about
the order at the time. A few of the early users - notably me - simply
liked the look of
const int c = 10;
better than
int const c = 10;
at the time.
I may have been influenced by the fact that my earliest examples
were written using "readonly" and
readonly int c = 10;
does read better than
int readonly c = 10;
The earliest (C or C++) code using "const" appears to have been
created (by me) by a global substitution of "const" for "readonly" ).
The const qualifiers of class member functions
mean that you are allowed to call that member function even when the object
of that class is const.
Thus:
struct A { void m(void) const; }; A const x; x.m(); // Only ok when `m()' is marked `const'.
Member functions should be marked const if and only if it is garanteed that by calling them the object is not changed.
For example:
class A { private: A* M_a; public: A(void) : M_a(this) { } A& get_a(void) const { return *M_a; } // ... };
This example shows that even if we return a non-const reference to the very same object (M_a was initialized with this on creation), the function get_a is still constant: It doesn't change the object at all, it just returns the value of M_a (get_a() is an accessor).
Trying to use const for class member functions for other reasons is wrong. Because the given class allows to change a constant object A we can conclude that the design of the class is wrong.
The following example is correct and shows how to use const for member functions for overloading purposes.
class B { }; class A { private: B M_b; public: A(B const& b) : M_b(b) { } B& b(void) // Non-const member function { return M_b; } // because we return directly // a non-const reference to // to a member. B const& b(void) const // Overloaded for constant object. { return M_b; } };
These two examples are very closely related. The difference is that in the second case we know first hand that the returned reference is a reference to a member of the class, while in the first example M_a can in principle point to any instance. Technically there is no difference between get_a and the first b(void), the only difference is the design of the two classes (bad design versus good design).
You can use the following rule of thumbs to decide what to do:
Note that one method to correct the bad design of the first example is to remove the get_a(void) method and add one or more member functions to take over the operations on A that are now apparently performed by the functions that call get_a(). Another method would be to try making M_a a const-pointer (A const*); perhaps the program never uses non-const access.