Tuesday, May 29, 2012

The C++ Pointer to Member Operators

Yesterday was the Memorial Day holiday in the U.S. and it turned out to be an unusually good day for me.

2001 Triumph Bonneville (Port)

I started out by taking an early morning ride on my Triumph Bonneville. I stopped for breakfast at one of my favorite coffee shops in Boulder Colorado, Caffe Sole, where I got a chapter read in Dixit and Nalebuff's popular introduction to basic game theory, Thinking Strategically [Norton, 1993]. I had lunch with Mrs. Overclock at a deli we'd never eaten at before while on our way to a hardware store that I'd never been to before (but she had), Harbor Freight, where I discovered an economically priced solar charge regulator for one of my projects. We finished the afternoon and evening at a cookout with some good friends of ours where I may have indulged in a few fine brewed adult beverages.

Somewhere amongst all those activities I discovered that I have not in fact memorized Stroustrup's epic reference book The C++ Programming Language [Addison-Wesley, 1997]. A big Thank You to Nishant in the LinkedIn.com group C++ Professionals for that little tidbit regarding the .* and ->* operators in C++. I was so surprised by this revelation that I had to write a little test program to convince myself how they worked. Here is the complete source code below annotated with comments. The remarkable part is in the second half of the program. I used both references and pointers, and both fields and methods, just to demonstrate to myself how all of it worked.


#include <cstdio>

class Foo {
public:
    int field;
    void method() { printf("Foo@%p::method(): field=%d\n", this, field); }
};

int main()
{
    // We declare an object named bar of type foo.

    Foo bar;

    // We declare a reference to bar named barr
    // and a pointer to bar named barp.

    Foo &barr = bar;
    Foo * barp = &bar;

    // We use the usual mechanisms to set a field
    // of bar to 1 and to invoke a method of bar.

    barr.field = 1;
    barp->method();

    // We declare a pointer to the field and a pointer
    // to the method in _any_ object of type Foo. Note
    // that this has absolutely _no_ mention of any
    // specific object of type of Foo, like for example
    // bar. Perhaps the implementation is storing the
    // offsets to the field and method in the variables.

    int Foo::*fieldp = &Foo::field;
    void (Foo::*methodp)() = &Foo::method;

    // We use the .* and ->* operators to set a field
    // of bar to 2 and invoke a method of bar.

    barr.*fieldp = 2;
    (barp->*methodp)();

    return 0;
}

And when you compile this little marvel for i686 using GNU C++ 4.4.3 and run it on Ubuntu GNU/Linux 2.6.32-40, here is what you get.

coverclock@silver:misc$ g++ -o pointer pointer.cpp
coverclock@silver:misc$ ./pointer
Foo@0xbff1841c::method(): field=1
Foo@0xbff1841c::method(): field=2

Stroustrup calls this Pointers to Data Members (section C.12, p. 853 in my hardbound Special Edition), although as he describes and as you can see above, it applies equally to fields or methods.

Why use this mechanism? Because an alternative is a generic pointer to any variable of the same type or any function with the same signature anywhere in the code base. These pointer-to-member operators are more type-safe in that they can apply only to a field of the specified type or method of the specified signature in an object of the specified class; it's a far more restrictive paradigm.

No comments: