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.0Invoking 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:
- The runtime takes the base address of
instance(thethispointer). - It adds the numeric byte offset stored inside
ptr. - 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
privatemember 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
privatemember 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.