Chapter 15: Classes having pointers to members

Don't hesitate to send in feedback: send an e-mail if you like the C++ Annotations; if you think that important material was omitted; if you find errors or typos in the text or the code examples; or if you just feel like e-mailing. Send your e-mail to Frank B. Brokken.

Please state the document version you're referring to, as found in the title (in this document: 7.0.0) and please state chapter and paragraph name or number you're referring to.

All received mail is processed conscientiously, and received suggestions for improvements will usually have been processed by the time a new version of the Annotations is released. Except for the incidental case I will normally not acknowledge the receipt of suggestions for improvements. Please don't interpret this as me not appreciating your efforts.

Classes having pointer data members have been discussed in detail in chapter 7. 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 pointers to members . Even if the return types and parameter types of member functions 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 let a char const *(*d_sp)() const data member point 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 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 irrelevant. 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's 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 as a pointer only in the context of the class String. It is defined as a pointer to a function in the class String, not expecting arguments, not modifying its object's data, 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 defining a pointer to a data member. Assume the class String contains a string d_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 following pointer to a global function
    char const * (*sp)() const;
becomes a pointer to a member function after prefixing the class-scope:
    char const * (String::*sp)() const;
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 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. This can be considered a kind of relative address: relative to the object for which the function is called. 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). In the example itself, note the use of the &-operator to determine the addresses of the members. These operators, as well as the class-scopes are required. Even when used inside the class member implementations themselves:

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

    inline unsigned PointerDemo::get() const
    {
        return d_value;
    }

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

        getPtr   = &PointerDemo::get;           // assignment
        valuePtr = &PointerDemo::d_value;
    }
Actually, nothing special is 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 be given the PointerDemo class scope. Pointers to members can also be used with virtual member functions. No further changes are required if, e.g., get() is defined as a virtual member function.

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 to. 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 member in combination 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 other hand, the notion of dereferencing: a dereference operation is used to reach the function or variable the pointer to member points to.

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 d_value;
            unsigned get() const;
    };

    inline unsigned PointerDemo::get() const
    {
        return d_value;
    }

    using namespace std;

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

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

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

        ptr->*valuePtr = 54321;                 // (3)
        cout << object.d_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 holding a person's name, address and phone number. Let's assume we want to construct a Person data base of employees. The employee data base can be queried, but 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 address() 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 employee 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 see all information about the employees, a SALESPERSON is allowed to see the employee's phone numbers, while the CLERK is only allowed to verify whether a person is actually a member of the organization.

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

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

        if (!p)
            return "not found";

        switch (d_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 nonetheless be evaluated every time personCode() is called. Instead of using a switch, we could define a member d_infoPtr as a pointer to a member function of the class PersonData returning a string and expecting a Person reference 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, assuming that the employee status is given as an argument to the constructor of the PersonData object.

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

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

        return p ? (this->*d_infoPtr)(p) :  "not found";
    }
Note the syntactical construction when using a pointer to member from within a class: this->*d_infoPtr.

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

    class PersonData
    {
        string (PersonData::*d_infoPtr)(Person *p);
    };
Finally, the constructor must initialize d_infoPtr to point to the correct member function. The constructor could, for example, be given the following code (showing only the pertinent code):
    PersonData::PersonData(PersonData::EmployeeCategory cat)
    {
        switch (cat)
        {
            case BOARD:
            case SUPERVISOR:
                d_infoPtr = &PersonData::allInfo;
            case SALESPERSON:
                d_infoPtr = &PersonData::noPhone;
            case CLERK:
                d_infoPtr = &PersonData::nameOnly;
        }
    }
Note how addresses of member functions are determined: 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. They exist separately from any object of their class. When these static members are public, they can be accessed as global entities, albeit that their class names are required when they are used.

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 usually be accessed like global entities (but see section 10.2.1). 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 associated objects, 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)() = String::n_strings;
                    // address of the  static member function

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

15.5: Pointer sizes

A peculiar characteristic of pointers to members is that their sizes differ from those of `normal' pointers. Consider the following little program:
    #include <string>
    #include <iostream>

    class X
    {
        public:
            void fun();
            string d_str;
    };
    inline void X::fun()
    {
        std::cout << "hello\n";
    }

    using namespace std;

    int main()
    {
        cout
        << "size of pointer to data-member:     " << sizeof(&X::d_str) << "\n"
        << "size of pointer to member function: " << sizeof(&X::fun) << "\n"
        << "size of pointer to non-member data: " << sizeof(char *) << "\n"
        << "size of pointer to free function:   " << sizeof(&printf) << endl;
    }

    /*
        generated output:

        size of pointer to data-member:     4
        size of pointer to member function: 8
        size of pointer to non-member data: 4
        size of pointer to free function:   4
    */
Note that the size of a pointer to a member function is eight bytes, whereas all other pointers are four bytes (Using the Gnu g++ compiler).

In general, these pointer sizes are not explicitly used, but their differing sizes may cause some confusion in statements like:

    printf("%p", &X::fun);
Of course, printf() is likely not the right tool to produce the value of these C++ specific pointers. The values of these pointers can be inserted into streams when a union, reinterpreting the 8-byte pointers as a series of size_t char values, is used:
    #include <string>
    #include <iostream>
    #include <iomanip>

    class X
    {
        public:
            void fun();
            std::string d_str;
    };

        inline void X::fun()
        {
            std::cout << "hello\n";
        }

    using namespace std;

    int main()
    {
        union
        {
            void (X::*f)();
            unsigned char *cp;
        }
            u = { &X::fun };

        cout.fill('0');
        cout << hex;
        for (unsigned idx = sizeof(void (X::*)()); idx-- > 0; )
            cout << setw(2) << static_cast<unsigned>(u.cp[idx]);
        cout << endl;
    }