Chapter 15: Classes having pointers to members

We're always interested in getting feedback. E-mail us if you like this guide, if you think that important material is omitted, if you encounter errors in the code examples or in the documentation, if you find any typos, or generally just if you feel like e-mailing. Send your email to Frank Brokken.

Please state the document version you're referring to, as found in the title (in this document: 5.2.0a) and please state the paragraph you're referring to.

All mail received is seriously considered, and new (sub)releases of the Annotations will normally reflect your suggestions for improvements. Except for the incidental case I will not otherwise acknowledge the receipt of suggestions for improvements. Please don't misinterpret this for lack of appreciation.

Classes having pointer data members have been discussed in detail in chapter 6. As we have seen, when pointer data-members occur in classes, such classes deserve some special treatment.

By now it is well known how to treat pointer data members: constructors are used to initialize pointers, destructors are needed to delete the memory pointed to by the pointer data members.

Furthermore, in classes having pointer data members copy constructors and overloaded assignment operators are normally needed as well.

However, in some situations we do not need a pointer to an object, but rather a pointer to members of an object. In this chapter these special pointers are the topic of discussion.

15.1: Pointers to members: an example

Knowing how pointers to variables and objects are used does not intuitively lead to the concept of a pointer to members. Even if the return type and parameter types of a member function are taken into account, surprises are likely to be encountered. For example, consider the following class:
    class String
    { 
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
For this class, it is not possible to define a char const *(*d_sp)() const pointing to the get() member function of the String class: d_sp cannot be given the address of the member function get().

One of the reasons why this doesn't work is that the variable d_sp has a global scope, while the member function get() is defined within the String class, and has class scope. The fact that the variable d_sp is part of the String class is of no relevance. According to d_sp's definition, it points to a function living outside of the class.

Consequently, in order to define a pointer to a member (either data or function, but usually a function) of a class, the scope of the pointer must be within the class' scope. Doing so, a pointer to a member of the class String can be defined as

    char const *(String::*d_sp)() const;
So, due to the String:: prefix, d_sp is defined to be active only in the context of the class String. Here, it is defined as a pointer to a constant function, not expecting arguments, and returning a pointer to constant characters.

15.2: Defining pointers to members

Pointers to members are defined by prefixing the normal pointer notation with the appropriate class plus scope resolution operator. Therefore, in the previous section, we used char const * (String::*d_sp)() const to indicate: Actually, the normal procedure for constructing pointers can still be applied: Another example, this time to a pointer to a data member. Assume the class String contains a string text member. How to construct a pointer to this member? Again we follow the basic procedure: Alternatively, a very simple rule of thumb is For example, the pointer to a member function: Nothing in the above discussion forces us to define these pointers to members in the String class itself. The pointer to a member may be defined in the class (so it becomes a data member itself), or in another class, or as a local or global variable. In all of these cases the pointer to member variable can be given the address of the kind of member it points to. The important part is that a pointer to member can be initialized or assigned without the need for an object of the corresponding class.

Initializing or assigning an address to such a pointer does nothing but indicating to which member the pointer will point to. This addressing can be considered a relative address: relative to the first byte used by an object. Consequently, no object is required when pointers to members are initialized or assigned. On the other hand, while it is allowed to initialize or assign a pointer to member, it is (of course) not possible to access these members without an associated object.

In the following example initialization of and assignment to pointers to members is illustrated (for illustration purposes all members of PointerDemo are defined public):

    class PointerDemo
    {
        public:
            unsigned get() const
            {
                return value;
            }

            unsigned
                value;
    };

    int main()
    {                                           // initialization
        unsigned (PointerDemo::*getPtr)() = &PointerDemo::get;
        unsigned PointerDemo::*valuePtr   = &PointerDemo::value;

        getPtr   = &PointerDemo::get;           // assignment
        valuePtr = &PointerDemo::value;         
    }
            
Note that actually nothing special in involved: the difference with pointers at global scope is that we're now restricting ourselves to the scope of the PointerDemo class. Because of this restriction all pointer definitions and all variables whose addresses are used must now be given the PointerDemo class scope.

15.3: Using pointers to members

In the previous section we've seen how to define pointers to member functions. In order to use these pointers, an object is always required. With pointers operating at global scope, the dereferencing operator * is used to reach the object or value the pointer points at. With pointers to objects the field selector operator operating on pointers ( ->) or the field selector operating operating on objects (.) can be used to select appropriate members.

To use a pointer to a member with an object the pointer to member field selector ( .*) must be used. To use a pointer to a member via a pointer to an object the `pointer to member field selector through a pointer to an object' ( ->*) must be used. These two operators combine the notions of, on the one hand, a field selection (the . and -> parts) to reach the appropriate field in an object and, on the second hand, the notion of dereferencing: A dereference operation is used to reach the function or variable the pointer to member points at.

Using the example from the previous section, let's see how we can use the pointer to member function and the pointer to data member:

    #include <iostream>

    class PointerDemo
    {
        public:
            unsigned get() const
            {
                return value;
            }

            unsigned
                value;
    };

    int main()
    {                                           // initialization
        unsigned (PointerDemo::*getPtr)() = &PointerDemo::get;
        unsigned PointerDemo::*valuePtr   = &PointerDemo::value;

        PointerDemo
            object,                             // (1) (see text)
            *ptr = &object;

        object.*valuePtr = 12345;               // (2)
        cout << object.*valuePtr << endl;
        cout << object.value << endl;

        ptr->*valuePtr = 54321;                 // (3)
        cout << object.value << endl;

        cout << (object.*getPtr)() << endl;     // (4)
        cout << (ptr->*getPtr)() << endl;
    }
            
We note: Pointers to members can be used profitably in situations where a class has a member which behaves differently depending on, e.g., a configuration state. Consider once again a class Person from section 7.2. This class contains fields for a person's name, address and phone. Let's assume we want to construct a Person data base of emplyees. The employee data base can be queried, by depending on the kind of person querying the data base either the name, the name and phone number or all stored information about the person is made available. This implies that a member function like getAddress() must return something like `<not available>' in cases where the person querying the data base is not allowed to see the person's address, and the actual address in other cases.

Assume the emplyee data base is opened with an argument reflecting the status of the employee who wants to make some queries. The status could reflect his or her position in the organization, like BOARD, SUPERVISOR, SALESPERSON, or CLERK. The first two categories are allowed to all information about the employees, the SALESPERSON are allowed to see the emplyee's phone numbers, while the CLERK is only allowed to see whether a person is actually a member of the organization.

We now construct a member string getPersonInfo(char const *name) in the data base class. A standard implementation of this class could be:

    string PersonData::getPersonInfo(char const *name)
    {
        Person
            *p = lookup(name);      // see if `name' exists

        if (!p)
            return "not found";

        switch (category)
        {
            case BOARD:
            case SUPERVISOR:
                return allInfo(p);
            case SALESPERSON:
                return noPhone(p);
            case CLERK:
                return nameOnly(p);
        }
    }
Although it doesn't take much time, the switch must be evaluated every time getPersonCode() is called, and similar constructions are required in other functions that might show behavior conditional to the value of the category variable.

However, we can also define a member infoPtr as a pointer to a member function of the class PersonData returning a string and expecting a Person pointer as its argument. Note that this pointer can now be used to point to allInfo(), noPhone() or nameOnly(). Furthermore, the function that the pointer variable points to will be known by the time the PersonData object is constructed, as the employee status is given as an argument to the constructor of the PersonData object.

After having set the infoPtr member to the appropriate member function, the getPersonInfo() member function may now be rewritten:

    string PersonData::getPersonInfo(char const *name)
    {
        Person
            *p = lookup(name);      // see if `name' exists

        return p ? (this->*infoPtr)(p) :  "not found";
    }
Note the construction that is used for accessing a pointer to member within a class: this->*infoPtr.

The member infoPtr is defined as follows (within the class PersonData, omitting other members):

    class PersonData
    {
        private:
            string (PersonData::*infoPtr)(Person *p);
    };
Finally, the constructor must initialize infoPtr to point to the currect member function. The constructor could, for example, be given the following code (showing only the pertinent code):
    PersonData::PersonData(PersonData::EmplyeeCategory cat)
    {
        switch (cat)
        {
            case BOARD:
            case SUPERVISOR:
                infoPtr = &PersonData::allInfo;
            case SALESPERSON:
                infoPtr = &PersonData::noPhone;
            case CLERK:
                infoPtr = &PersonData::nameOnly;
        }
    }
Note the way the addresses of the member functions are taken: the class PersonData scope must be specified, even though we're already inside a member function of the class PersonData.

An example using pointers to data members is given in section 17.4.60, in the context of the stable_sort() generic algorithm.

15.4: Pointers to static members

Static members of a class exist without an object of their class. In other words, they can exist outside of any object of their class, and they have no this pointer. When these static members are public, they can be accessed as a global function, albeit that their class names are required when they are called.

Assume that a class String has a public static member function int n_strings(), returning the number of string objects created so far. Then, without using any String object the function String::n_strings() may be called:

    void fun()
    {
        cout << String::n_strings() << endl;
    }
Public static members can be treated as globally accessible functions and data. Private static members, on the other hand, can be accessed only from within the context of their class: they can only be accessed from inside the member functions of their class.

Since static members have no particular link with objects of their class, but are comparable to global functions and data, their addresses can be stored in ordinary pointer variables, operating at the global level. Actually, using a pointer to member to address a static member of a class would produce a compilation error.

For example, the address of a static member function int String::n_strings() can simply be stored in a variable int (*pfi)(), even though int (*pfi)() has nothing in common with the class String. This is illustrated in the next example:

    void fun()
    {
        int 
            (*pfi)();
            
        pfi = String::n_strings;    
                    // address of the  static member function

        cout << (*pfi)() << endl;
                    // print the value produced by
                    // String::n_strings()
    }