What Are Pointers-to-Members in C++?

Overview

A pointer is nothing but a variable that can hold an address of a memory location. The type of pointer is determined by the type of content it holds. This means that a pointer that is supposed to hold the address of an integer type variable must be declared as an integer type of pointer.

int i=10;
int *pi = &i;

A pointer assigned zero or null means it does not point to any memory location. However, we can change the content of a pointer at runtime and the destination can be either data or a function.

Pointer-to-Member

The interesting aspect of C++ pointer-to-member is that it points to the location inside a class. The point here is that, although a pointer needs an address to hold, it is only available when the class is instantiated during execution. Until then, we cannot produce an actual address combining that offset with the starting address of a particular object. Therefore, the syntax of pointer-to-member requires that we select an object at the same time we are dereferencing the pointer to a member.

This can be illustrated with a simple structure as follows:

typedef struct s{
   int info;
}node;

int main()
{
   node nodeObj,*ptr2NodeObj = &nodeObj;
   ptr2NodeObj->info = 10;
   cout<<"Pointer to node object: "<<ptr2NodeObj->
      info<<endl;
   nodeObj.info = 20;
   cout<<"info in node object updated to :"<<nodeObj.info
      <<endl;
   return 0;
}

Note that if had ptr2NodeObj been an ordinary pointer to an integer, we would have dereferenced the pointer, as in the following:

*ptr2NodeObj = 10;

Now, consider what our access mechanism would be if we have a pointer that points to something inside a class object, although it does not represent an offset into the object? The obvious answer is that we must dereference it with *. But, note that it is an offset into an object; therefore, we also must take into account that particular object. Therefore, a mere * operator is not enough. We also must combine it with an object dereference, something like combining ->* for a pointer to an object and .* for an object or a reference.

Pointer-to-Member Operators

The C++ language has specific operators to represent pointer-to-member access. The .* and ->* operator function returns specific class member values for the object that it represents on the left side of an expression while the right side specifies a class member.

A Quick Example

Here is a quick example to illustrate the idea.

#include <iostream>
using namespace std;

class MyClass {
public:
   void greet() { cout << "Hello!n"; }
   int info;
};

int main() {
   // Pointer to 'greet' member funtion
   void (MyClass::*p2gfn)() = &MyClass::greet;
   // Pointer to 'info' member variable
   int MyClass::*pinfo = &MyClass::info;

   // Class object
   MyClass obj;
   // Pointer to class object
   MyClass *pobj = new MyClass;

   // Member 'info' access: assign some values
   obj.*pinfo = 10;
   pobj->*pinfo = 20;

   cout << obj.*pinfo << endl
      << pobj->*pinfo << endl;

   // Member function 'greet()' call
   (obj.*p2gfn)();
   (pobj->*p2gfn)();

   delete pobj;
   return 0;
}

Observe how a pointer-to-member, p2gfn, invokes the class member function called greet. Another pointer, called pinfo, is used to access the integer type class member variable info.

We can compare how two operators associate with the operand.

The binary operator .* combines the following:

  • First operand: An object of class type
  • Second operand: A pointer to member type

The binary operator ->*, on the other hand, combines

  • First operand: pointer to an object of class type
  • Second operand: a pointer to member type

Member Variables

Unlike ordinary pointers, here with pointer-to-member we have to specify what type of class it is pointing at and use * while defining it. We also must specify the class that this pointer-to-member is associated with. Therefore, to accomplish this, we use the name of the class and the scope resolution operator.

Note that in the following code snippet we not only have defined a pointer-to-member variable called pinfo, but also initialized it.

int MyClass::*pinfo = &MyClass::info;

The interesting aspect is that there is actually no address of MyClass::info because it is referring to the class and not the object of that class. Thus, &MyClass::info is actually possible with pointer-to-member syntax only.

Member Functions

The pointer-to-member syntax for member functions also are similar. We define the pointer to function as follows:

int (*funcptr)(double);

The parenthesis around the pointer variable denotes a function pointer; otherwise, the compiler would treat it as a function that returns an integer pointer. The parenthesis also plays a vital role in defining a pointer to a member function of a class.

void (MyClass::*p2gfn)() = &MyClass::greet;

Observe that how we have defined a pointer to the member function by using the class name and the scope resolution operator with an ordinary function pointer definition. To access the member function, we also must assign the address of the function to the function pointer. Because it is a member function of a class, we also must use a class name with the scope resolution operator to force the compiler to evaluate the definition accordingly.

Another Quick Example

#include <iostream>
using namespace std;

class Bird
{
public:
   void fly() const { cout<<"Flying..."<<endl;}
   void chirp() const { cout<<"twitter..tweet"
      <<endl;}
};

int main() {
   Bird b;
   Bird *pb = &b;

   void (Bird::*p2fly)() const = &Bird::fly;
   (b.*p2fly)();
   (pb->*p2fly)();

   return 0;
}

The power of the pointer is that we can change what it points to any time during execution. This leverages programming flexibility. We can change its behavior at runtime. The pointer-to-member enhances the capability of the pointer. It empowers us to choose a class member at runtime. This also means that we can access only the publicly visible members and does not violate the encapsulation norms.

Now, the pointer-to-member is a somewhat advanced concept and is not expected to be used by a novice programmer. However, if the syntactical expression of pointer-to-member seems complex, we can always use typedef to make it look simpler.

It is good idea to use the pointer-to-member as part of the internal implementation mechanism to make the code look more clean and simpler to the end-user.

Here is a quick example.

#include <iostream>
using namespace std;

class Bird
{
   void fly() const { cout<<"Flying..."<<endl;}
   void chirp() const { cout<<"twitter..tweet"
      <<endl;}
   void hop() const { cout<<"hop...hop"<<endl;}
   void peck() const { cout<<"pick...pick"
      <<endl;}
   enum {max = 4};
   void (Bird::*p2f[max])() const;
public:
   Bird() {
      p2f[0] = &Bird::fly;
      p2f[1] = &Bird::chirp;
      p2f[2] = &Bird::hop;
      p2f[3] = &Bird::peck;
   }
   void choose(int i){
      if(i<0||i>max) return;
      (this->*p2f[i])();
   }
   int getMax(){return max;}
};

int main() {
   Bird b;
   for(int i=0;i<b.getMax();i++)
      b.choose(i);
   return 0;
}

Conclusion

The function of pointers-to-members is almost same as with ordinary pointers. We can choose a specific area of memory (such as data or function) during execution. It is just that pointers-to-members tunes with members of the class instead of global data or functions with additional flexibility to manipulate pointer behavior at runtime.

Manoj Debnath
Manoj Debnath
A teacher(Professor), actively involved in publishing, research and programming for almost two decades. Authored several articles for reputed sites like CodeGuru, Developer, DevX, Database Journal etc. Some of his research interest lies in the area of programming languages, database, compiler, web/enterprise development etc.

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read