C++ Pointer-to-Member Operators Explained

Pointer-to-member operators in C++ allow developers to reference, store, and invoke class members dynamically without binding them to a specific instance at compile time. This article provides a comprehensive overview of how pointer-to-member operators (.* and ->*) function under the hood, how they differ from conventional memory pointers, and the mechanisms through which they read and modify an object's internal state.


What Are Pointer-to-Member Operators?

In C++, regular pointers store the absolute memory address of an object, variable, or free function. In contrast, a pointer to a member points to a specific attribute or method within a class layout rather than an address in memory.

Because a member variable or function cannot exist independently of a class instance, a pointer-to-member requires an instance of that class to be dereferenced. C++ provides two specialized binary operators for this purpose:

  • .* (Direct Member Pointer Operator): Used when dereferencing a member pointer against an actual object instance or reference.
  • ->* (Indirect Member Pointer Operator): Used when dereferencing a member pointer against a pointer to an object.

Declaring Pointers to Members

A pointer to a member specifies the enclosing class scope along with the member's underlying type.

1. Pointer to Member Data

To store an offset to an internal state variable:

class Account {
public:
    double balance;
    int id;
};

// Syntax: DataType ClassName::* PointerName
double Account::*balancePtr = &Account::balance;

2. Pointer to Member Function

To store a reference to an internal member function:

class Account {
public:
    void deposit(double amount);
};

// Syntax: ReturnType (ClassName::* PointerName)(ParamTypes...)
void (Account::*funcPtr)(double) = &Account::deposit;

Accessing Internal State via .* and ->*

Once declared, these pointers are applied to class instances to manipulate internal state.

Reading and Modifying Member Variables

Account myAccount{100.0, 1};
Account* accountPtr = &myAccount;

double Account::*statePtr = &Account::balance;

// Modifying state using an object instance (.*)
myAccount.*statePtr = 250.0;

// Reading state using a pointer to an instance (->*)
double currentBalance = accountPtr->*statePtr; // Evaluates to 250.0

Invoking Member Functions

When calling member functions through pointers, parentheses are mandatory due to operator precedence:

Account myAccount;
Account* accountPtr = &myAccount;

void (Account::*action)(double) = &Account::deposit;

// Calling on an instance
(myAccount.*action)(50.0);

// Calling via an instance pointer
(accountPtr->*action)(75.0);

How Pointers to Members Function Internally

1. Data Members as Offsets

At the hardware and compiler level, a pointer to a data member is typically implemented as a byte offset from the starting address of the class instance.

When the expression instance.*ptr is evaluated:

  1. The runtime takes the base address of instance (the this pointer).
  2. It adds the numeric byte offset stored inside ptr.
  3. It accesses the memory located at that calculated address.

Because it represents an offset rather than an absolute address, the same member pointer can be reused across thousands of distinct object instances to access that specific internal state variable.

2. Member Functions and the this Pointer

Standard non-static member functions require an implicit this pointer argument passing the address of the target object. When utilizing (instance.*funcPtr)(), the compiler translates the invocation by passing the address of instance as the hidden first argument to the function defined by funcPtr.

3. Virtual Function Resolution

If a pointer points to a virtual member function, the pointer-to-member implementation includes metadata signaling that the call must resolve dynamically. When invoked via .* or ->*, C++ resolves the call through the virtual method table (vtable) of the runtime instance, preserving polymorphism.


Encapsulation and Access Control

Pointer-to-member operators do not bypass C++ access specifiers (private, protected, public).

  • You cannot take the address of a private member outside of the class scope or without friend access.
  • Access rules are checked at the time the pointer is initialized, not when it is dereferenced.
  • If a class function with appropriate permissions generates a pointer to a private member and returns it, external code holding that pointer can use it with .* or ->* to access that private internal state, because the permission was granted during address generation.