Previous Table of Contents Next


8.2.3.4. Constant Pointers to Members and Pointers to Constant Members

What if we want to declare a pointer such as cgp with the restriction that the pointer itself may not change? With ordinary pointers, we look for a place to say * const, so with member pointers, we similarly look for a place to say ::* const. Knowing that, it is easy to see how to say it:

   int (Thing:: *const cgpc)() const = &Thing::get;

Again, there is no difficulty saying

   const Thing ct;
   int i = (ct.*cgpc)();

8.2.3.5. const Summary

The first step in understanding a declaration is finding the boundary between the specifiers and the first declarator. The specifiers are keywords and type names; the declarators are everything afterwards. Because a * is neither a keyword nor a type name, an explicit * is always part of a declarator.

Changing the order of the specifiers doesn’t change their meaning.

Saying * const (which, because of the *, is always part of a declarator) denotes an immutable pointer.

Saying ::* const always denotes an immutable member pointer.

That leaves the possibility of saying const at the end of a declarator, which always says something about a constant member function.

Finally, one rule supersedes all the others: If you don’t understand what you’re saying, don’t say it. Study it until you understand it, rewrite it in a simpler form, or break it into pieces you understand.

8.2.4. Constructor Initializers

Constructor initializers are one of the C++ facilities that beginners most often misunderstand. The unique initializer syntax is just common enough that virtually all C++ programmers must learn to use them, but just rare enough to be hard to understand at first.

The idea behind constructor initializers is simple enough. We can build a class on top of components of other types by using objects of those types either as members or as base classes. When we say how to initialize an object of our class, we must then say how to initialize those components.

We say how to initialize an object of our class by writing a constructor for that class. It is therefore tempting to write assignment statements in such a constructor to initialize the components:

   class Person {
   public:
        Person() { name = “”; address = “”; }
        Person (string n, string a) { name = n; address = a; }
     // ...
   private:
        string name, address;
   };

Although this example is not strictly wrong, it is dubious. The reason is that by the time control has reached the { of any of the constructors, name and address have already been initialized using the default constructor for the string class. If string is the class from the standard library, the default value is a null string. Then, once name and address have been initialized, the constructor gives them brand new values, obliterating the initial values that were already put in place.

It is much better to give the initial values directly:

   class Person {
   public:
        Person(): name (“”), address(“”) { }
        Person (string n, string a): name(n), address(a) { }
        // ...
   private:
        string name, address;
   };

or, if the constructors were defined separately, as

   class Person {
   public:
        Person();
        Person(string, string);
        // ...
   private:
        string name, address;
   };
   Person::Person(): name(“”), address(“”) { }
   Person::Person(string n, a): name(n), address(a) { }

In either case, we are now saying that name and address start off immediately with their given values.

We can even do slightly better: Instead of initializing name and address explicitly to null string literals, we can leave them uninitialized:

   class Person {
   public:
        Person () { }
        Person(string n, string a): name(n), address(a) { }
        // ...
   private:
        string name, address;
   };

By doing so, we allow name and address to be initialized automatically to their natural default values and avoid the extra overhead of explicitly converting “” to a string in order to initialize name and address.

8.2.5. Summary of Syntactic Pitfalls

By far, the most common syntactic problems in C++ programs have to do with declarations. Part of the difficulty comes from the need for C++ to express a much wider range of declarations than is possible in C, while still retaining a syntax that is compatible with C. Another part comes about because of the C++ distinction between assignment and initialization, which C programmers can generally ignore. In C++, as in C, there is not much difference between writing

   int n = 0

and

   int n;
   n = 0;

The distinction shows up in both C and C++ in declarations such as

   const int n;                              // error--no initial value

but C programmers wouldn’t write such a declaration anyway because there would be no way to give a value to n after declaring it.

The distinction becomes important when we switch from integers to types whose constructors do real work, such as

   string s = “Hello, world”;

versus

   string s;
   s = “Hello, world”;

The distinction becomes even more important in the presence of user-defined types that have no default constructors and that therefore require every object of such a type to be initialized explicitly.

To avoid these pitfalls, then, one must avoid assuming that because part of the C++ declaration syntax works just like C, the rest of the syntax also works the same way.


Previous Table of Contents Next