Chapter 16: 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: 10.3.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 are usually 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 9. Classes defining pointer data-members deserve some special attention, as they usually require the definitions of copy constructors, overloaded assignment operators and destructors

Situations exist where we do not need a pointer to an object but rather a pointer to members of a class. Pointers to members can profitably be used to configure the behavior of objects of classes. Depending on which member a pointer to a member points to objects will show certain behavior.

Although pointers to members have their use, polymorphism can frequently be used to realize comparable behavior. Consider a class having a member process performing one of a series of alternate behaviors. Instead of selecting the behavior of choice at object construction time the class could use the interface of some (abstract) base class, passing an object of some derived class to its constructor and could thus configure its behavior. This allows for easy, extensible and flexible configuration, but access to the class's data members would be less flexible and would possibly require the use of `friend' declarations. In such cases pointers to members may actually be preferred as this allows for (somewhat less flexible) configuration as well as direct access to a class's data members.

So the choice apparently is between on the one hand ease of configuration and on the other hand ease of access to a class's data members. In this chapter we'll concentrate on pointers to members, investigating what these pointers have to offer.

16.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 can easily 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 char const *(*d_sp)() const point to the String::get member function as 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 (it is a pointer to a function, not a pointer to a function within String), while the member function get is defined within the String class, and thus has class scope. The fact that d_sp is a data member of the class String is irrelevant here. According to d_sp's definition, it points to a function living somewhere outside of the class.

Consequently, to define a pointer to a member (either data or function, but usually a function) of a class, the scope of the pointer must indicate class scope. Doing so, a pointer to the member String::get is defined like this:

    char const *(String::*d_sp)() const;
So, by prefixing the *d_sp pointer data member by String::, it is defined as a pointer in the context of the class String. According to its definition it is 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.

16.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 that d_sp

The prototype of a matching function is therefore:

    char const *String::somefun() const;
which is any const parameterless function in the class String, returning a char const *.

When defining pointers to members the standard procedure for constructing pointers to functions can still be applied:

Here is another example, 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 standard 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 forces us to define pointers to members in their target (String) classes. Pointers to members may be defined in their target classes (so they become data members), or in another class, or as a local variable or as a 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 requiring the existence an object of the pointer's target class.

Initializing or assigning an address to such a pointer merely indicates to which member the pointer points. This can be considered some 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. While it is allowed to initialize or assign a pointer to member, it is (of course) not possible to call those members without specifying an object of the correct type.

In the following example initialization of and assignment to pointers to members is illustrated (for illustration purposes all members of the class PointerDemo are defined public). In the example itself the &-operator is used to determine the addresses of the members. These operators as well as the class-scopes are required. Even when used inside member implementations:

    #include <cstddef>

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

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

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

        getPtr   = &PointerDemo::get;           // assignment
        valuePtr = &PointerDemo::d_value;
    }

This involves nothing special. 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 special syntax is required when pointing to virtual members. Pointer construction, initialization and assignment is done identically to the way it is done with non-virtual members.

16.3: Using pointers to members

Using pointers to members to call a member function requires the existence of an object of the class of the members to which the pointer to member refers to. With pointers operating at global scope, the dereferencing operator * is used. 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 specified. 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 specified. These two operators combine the notions of a field selection (the . and -> parts) to reach the appropriate field in an object and 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 pointers to member functions and pointers to data members:

    #include <iostream>

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

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

    using namespace std;

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

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

        object.*valuePtr = 12345;               // (2)
        cout << object.*valuePtr << '\n' <<
                object.d_value << '\n';

        ptr->*valuePtr = 54321;                 // (3)
        cout << object.d_value << '\n' <<
                (object.*getPtr)() << '\n' <<   // (4)
                (ptr->*getPtr)() << '\n';
    }

We note:

Pointers to members can be used profitably in situations where a class has a member that behaves differently depending on a configuration setting. Consider once again the class Person from section 9.3. Person defines data members holding a person's name, address and phone number. Assume we want to construct a Person database of employees. The employee database can be queried, but depending on the kind of person querying the database 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 database is not allowed to see the person's address, and the actual address in other cases.

The employee database is opened specifying 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 database 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 personInfo 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 pointer to a Person as its argument.

Instead of evaluating the switch this pointer can be used to point to allInfo, noPhone or nameOnly. Furthermore, the member function the pointer points to will be known by the time the PersonData object is constructed and so its value needs to be determined only once (at the PersonData object's construction time).

Having initialized d_infoPtr the personInfo member function is now implemented simply as:

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

        return p ? (this->*d_infoPtr)(p) :  "not found";
    }

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 initializes d_infoPtr. This could be realized using a simple switch:
    PersonData::PersonData(PersonData::EmployeeCategory cat)
    :
        switch (cat)
        {
            case BOARD:
            case SUPERVISOR:
                d_infoPtr = &PersonData::allInfo;
            break;
            case SALESPERSON:
                d_infoPtr = &PersonData::noPhone;
            break;
            case CLERK:
                d_infoPtr = &PersonData::nameOnly;
            break;
        }
    }
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 provided in section 19.1.60, in the context of the stable_sort generic algorithm.

16.4: Pointers to static members

Static members of a class can be used without having available an object of their class. Public static members can be called like free functions, albeit that their class names must be specified when they are called.

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

    void fun()
    {
        cout << String::count() << '\n';
    }
Public static members can be called like free functions (but see also section 8.2.1). Private static members can only be called within the context of their class, by their class's member or friend functions.

Since static members have no associated objects their addresses can be stored in ordinary function pointer variables, operating at the global level. Pointers to members cannot be used to store addresses of static members. Example:

    void fun()
    {
        size_t (*pf)() = String::count;
                // initialize pf with the address of a static member function

        cout << (*pf)() << '\n';
                // displays the value returned by String::count()
    }

16.5: Pointer sizes

An interesting 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();
            std::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) << '\n';
    }

    /*
        generated output (on 32-bit architectures):

        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
    */

On a 32-bit architecture a pointer to a member function requires eight bytes, whereas other kind of pointers require four bytes (Using Gnu's g++ compiler).

Pointer sizes are hardly ever explicitly used, but their sizes may cause 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 << '\n';
    }