Previous | Table of Contents | Next |
8.2.3.2. Constant Pointers and Pointers to Constants
Now lets see how const and * interact. First, heres a common case:
const int* cp;
By looking for the first non-type, we see that the specifiers are const and int and the declarator is *cp. That means that we should treat const int as a unit, as if const modifies int. Viewed that way, it should be easy to see that cp is a pointer to a constant integer object. Moreover, changing the declaration to
int const* cp;
does not affect its meaning because the specifiers may be reordered freely.
If we want to say that the pointer itself is constant, we must move the const into the declarator:
int *const pc = &i;
and indeed this declaration requires an initializer because we wont be able to give pc a value later. Again applying the rule, we see that the * marks the beginning of the declarator, which means that the const is now part of the declarator and not part of the specifiers. Here is an example to emphasize this distinction:
int i, *const pc = &i;
This declaration has two declarators, namely i and *const pc.
Note that our rule implies that if a declaration has a * in it, that * is never part of the specifiers. The * always says that something is a pointer; to say that the pointer is a constant pointer, we always follow the * by const. In other words, to declare a constant pointer directly, look for a place to say *const.
Locating such a place can be confusing because it is possible to use typedef to define pointer types that conceal the *:
typedef int* IP;
In this declaration, the specifiers are typedef and int and the declarator is *IP. Once we have it, we can say
const IP p = &i;
which declares a pointer without using a *. What is the type of p?
Applying our rule again, we find that the specifiers are const IP (because IP is a type name) and the declarator is p. Therefore, it appears that the const modifies IP and p is therefore a constant pointer. Indeed, that is exactly what happens: It is as if we had written
int* const p = &i;
Indeed, it might be clearer if we had written
IP const p = &i;
To confirm your understanding of where the declarator begins, note that in
int* const p = &i;
the declarator is * const p, but in
IP const p = &i;
the declarator is just p.
From the foregoing examples, we learn that in the following example, the types of cp and pc are different:
const IP pc = &i; // constant pointer to int const int* cp = &i; // pointer to constant int
because pc is a constant pointer and cp points to a constant. It is very important that you understand this distinction; if you dont, you might want to read this section again until you do.
8.2.3.3. Adding Member Functions and Pointers to Members
All the properties of const that we have considered so far are part of the syntax that C adopted from C++. In addition, C++ has the notions of member functions and pointers to members. Fortunately, the way C++ handles these notions fits well with how C does it, so once youve mastered the complexities of C declarations, the C++ rules arent much harder. Accordingly, the following discussion does not describe a single, obvious source of misconceptions. Rather, the interactions between const, type declarations, and pointers to members are rich enough, and can occur in enough contexts, that the rules may not be easy to grasp at first. The main thing to remember while reading the discussion that follows is that these rules exist and can be looked up as needed.
Recall that classes may have constant member functions, which promise not to change the value of their objects:
class Thing { public: int get() const; void put (int); };
Here weve said that class Thing has two member functions called get and put. If t is a Thing, then calling t.put(42) might change the value of t, but calling t.get() does not.
Notice that when we declare a constant member function, the const goes at the end of the declarator. That now gives us three possible places for const to go, so that
class X { public: const int* const f() const; };
declares a class with a member function called f that returns a pointer to a constant integer (the first const) that cannot itself be modified (the second const). Moreover, the function promises not to modify its object (the third const).
Recall that we can declare pointers to class members:
void (Thing::*pp)(int) = &Thing::put;
declares pp as a variable that can potentially contain a pointer to any member function of class Thing that accepts an int argument and returns void. Of course, there happens to be only one such function in this case, namely Thing::put.
Suppose we want to declare a member pointer that could point to Thing::get. It might seem obvious that we could do it this way:
int (Thing::*gp)() = &Thing::get; // error
but when we try it, we find that it doesnt work. The reason is that were allowed to call get only on behalf of a const object:
const Thing ct; int i = ct.get();
but if we try to do the same with our member pointer, the compiler stops us:
const Thing ct; int i = (ct.*gp)(); // error
Although get promises not to change its object, that promise is not carried along into gp. After all, gp could potentially point to some other member of Thing that might change its object, in which case evaluating (ct.*gp)() changes ct even though ct is not allowed to change. We therefore need some way of declaring a pointer that is like gp except that it must be restricted to pointing only at constant member functions.
The way to do that follows the form of the member function declaration itself:
int (Thing::*cgp)() const = &Thing::get;
Now we can say, for example
const Thing ct; int i = (ct.*cgp)();
with no problems.
Previous | Table of Contents | Next |