MFC Programmer's SourceBook : Thinking in C++
Bruce Eckel's Thinking in C++, 2nd Ed Contents | Prev | Next

Pointers to members

A pointer is a variable that holds the address of some location, which can be either data or a function, so you can change what a pointer selects at run-time. The C++ pointer-to-member follows this same concept, except that what it selects is a location inside a class. The dilemma here is that all a pointer needs is an address, but there is no “address” inside a class; selecting a member of a class means offsetting into that class. You can’t produce an actual address until you combine that offset with the starting address of a particular object. The syntax of pointers to members requires that you select an object at the same time you’re dereferencing the pointer to member.

To understand this syntax, consider a simple structure:

struct simple { int a; };

If you have a pointer sp and an object so for this structure, you can select members by saying

sp->a;
so.a;
Now suppose you have an ordinary pointer to an integer, ip. To access what ip is pointing to, you dereference the pointer with a *:

*ip = 4;

Finally, consider what happens if you have a pointer that happens to point to something inside a class object, even if it does in fact represent an offset into the object. To access what it’s pointing at, you must dereference it with *. But it’s an offset into an object, so you must also refer to that particular object. Thus, the * is combined with the object dereferencing. As an example using the simple class,

sp->*pm = 47;
so.*pm = 47; 
So the new syntax becomes –>* for a pointer to an object, and .* for the object or a reference. Now, what is the syntax for defining pm? Like any pointer, you have to say what type it’s pointing at, and you use a * in the definition. The only difference is you must say what class of objects this pointer-to-member is used with. Of course, this is accomplished with the name of the class and the scope resolution operator. Thus,

int simple::*pm;

You can also initialize the pointer-to-member when you define it (or any other time):

int simple::*pm = &simple::a;

There is actually no “address” of simple::a because you’re just referring to the class and not an object of that class. Thus, &simple::a can be used only as pointer-to-member syntax.

Functions

A similar exercise produces the pointer-to-member syntax for member functions. A pointer to a function is defined like this:

int (*fp)(float);

The parentheses around (*fp) are necessary to force the compiler to evaluate the definition properly. Without them this would appear to be a function that returns an int*.

To define and use a pointer to a member function, parentheses play a similarly important role. If you have a function inside a structure:

struct simple2 { int f(float); };

you define a pointer to that member function by inserting the class name and scope resolution operator into an ordinary function pointer definition:

int (simple2::*fp)(float);

You can also initialize it when you create it, or at any other time:

int (simple2::*fp)(float) = &simple2::f;

As with normal functions, the & is optional; you can give the function identifier without an argument list to mean the address:

fp = simple2::f;

An example

The value of a pointer is that you can change what it points to at run-time, which provides an important flexibility in your programming because through a pointer you can select or change behavior at run-time. A pointer-to-member is no different; it allows you to choose a member at run-time. Typically, your classes will have only member functions publicly visible (data members are usually considered part of the underlying implementation), so the following example selects member functions at run-time.

//: C11:Pmem.cpp
// Pointers to members

class Widget {
public:
  void f(int);
  void g(int);
  void h(int);
  void i(int);
};

void Widget::h(int) {}

int main() {
  Widget w;
  Widget* wp = &w;
  void (Widget::*pmem)(int) = &Widget::h;
  (w.*pmem)(1);
  (wp->*pmem)(2);
} ///:~ 

Of course, it isn’t particularly reasonable to expect the casual user to create such complicated expressions. If the user must directly manipulate a pointer-to-member, then a typedef is in order. To really clean things up, you can use the pointer-to-member as part of the internal implementation mechanism. Here’s the preceding example using a pointer-to-member inside the class. All the user needs to do is pass a number in to select a function. [31]

//: C11:Pmem2.cpp
// Pointers to members
#include <iostream>
using namespace std;

class Widget {
  void f(int) const {cout << "Widget::f()\n";}
  void g(int) const {cout << "Widget::g()\n";}
  void h(int) const {cout << "Widget::h()\n";}
  void i(int) const {cout << "Widget::i()\n";}
  enum { count = 4 };
  void (Widget::*fptr[count])(int) const;
public:
  Widget() {
    fptr[0] = &Widget::f; // Full spec required
    fptr[1] = &Widget::g;
    fptr[2] = &Widget::h;
    fptr[3] = &Widget::i;
  }
  void select(int i, int j) {
    if(i < 0 || i >= count) return;
    (this->*fptr[i])(j);
  }
  int Count() { return count; }
};

int main() {
  Widget w;
  for(int i = 0; i < w.Count(); i++)
    w.select(i, 47);
} ///:~ 

In the class interface and in main( ), you can see that the entire implementation, including the functions themselves, has been hidden away. The code must even ask for the Count( ) of functions. This way, the class implementor can change the quantity of functions in the underlying implementation without affecting the code where the class is used.

The initialization of the pointers-to-members in the constructor may seem overspecified. Shouldn’t you be able to say

fptr[1] = &g;

because the name g occurs in the member function, which is automatically in the scope of the class? The problem is this doesn’t conform to the pointer-to-member syntax, which is required so everyone, especially the compiler, can figure out what’s going on. Similarly, when the pointer-to-member is dereferenced, it seems like

(this->*fptr[i])(j);

is also over-specified; this looks redundant. Again, the syntax requires that a pointer-to-member always be bound to an object when it is dereferenced.


[31] Thanks to Owen Mortensen for this example

Contents | Prev | Next


Go to CodeGuru.com
Contact: webmaster@codeguru.com
© Copyright 1997-1999 CodeGuru