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 runtime. 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 runtime, which provides an important flexibility in your programming because through a pointer you can select or change behavior at runtime. A pointer-to-member is no different; it allows you to choose a member at runtime. 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 runtime.

//: 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. [37]

//: 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";}
  static const int _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.


[37] Thanks to Owen Mortensen for this example

Contents | Prev | Next


Contact: webmaster@codeguru.com
CodeGuru - the website for developers.
[an error occurred while processing this directive]