Pointers to Members

Learn how to create pointers to class functions and data members, and how to use them

Ryan McCombe
Published

Previously, we've seen how function pointers, and first-class functions in general, give us a lot of flexibility in designing our programs.

In the following example, our Party class has a for_each() method which accepts a function argument. It then invokes that function for every Player in the Party:

#include <iostream>

class Player {/*...*/}; class Party { public: void for_each(auto Func) { Func(PlayerOne); Func(PlayerTwo); Func(PlayerThree); } private: Player PlayerOne{"Roderick"}; Player PlayerTwo{"Anna"}; Player PlayerThree{"Steve"}; };

This allows the external code to execute an action for each Player. This design gives us a useful separation: the Party class implements and controls the iteration process, whilst external code can specify the behavior they want to happen on each iteration.

Below, our program uses a function pointer to provide behavior defined within the Log() function:

#include <iostream>

class Player {/*...*/};
class Party {/*...*/}; void LogName(Player& P) { std::cout << P.Name << '\n'; } int main() { Party MyParty; MyParty.for_each(LogName); }
Roderick
Anna
Steve

In this lesson, we'll expand our techniques to include member function pointers - that is, pointers to functions that are part of a class or struct:

#include <iostream>

class Player {
 public:
  void LogName() {
    std::cout << Name << '\n';
  }
  
  std::string Name;
};

class Party {/*...*/}; int main() { Party MyParty; // We want to use the `Player::LogName()` function MyParty.for_each(/* ??? */); }

This is more difficult than we might expect, so let's break down all the complexities.

Static and Non-Static Members

Most of this lesson deals specifically with non-static members - that is, members that are invoked within the context of some object that is a member of that class.

#include <iostream>

class Character {
 public:
  int GetHealth() { return Health; }
  int Health;
};

int main() {
  Character P1{50};
  Character P2{100};

  // Calling GetHealth() in the context of P1
  std::cout << P1.GetHealth() << '\n';

  // Calling GetHealth() in the context of P2
  std::cout << P2.GetHealth();
}
50
100

Within the body of a class function, that object is available through the this pointer, and any other class functions or variables that the function directly references will also be invoked in that context:

class Character {
public:
  int GetHealth() {
    // Equivalent to this->Health
    return Health;
  }
  
  bool isAlive() {
    // Equivalent to this->GetHealth()
    return GetHealth() <= 0;
  }
  
  int Health;
};

Most members work this way so when we define class members, they are non-static by default. However, C++ also allows us to define static members:

#include <iostream>

class Character {
 public:
  // Static member function
  static float GetArmor() { return Armor; }

  // Static data member declaration
  static float Armor;

  // Non-static data member
  int Health{100};
};

// Static data member definition
float Character::Armor = 0.2;

Each object of a class get its own copy of non-static members which can be updated independently, but the value of non-static members is shared shared between all members of the class. That is, they point to the exact same memory location:

#include <iostream>

class Character {/*...*/}; int main() { Character A; Character B; if (&A.Armor == &B.Armor) { std::cout << "Same Memory Address\n"; } if (&A.Health != &B.Health) { std::cout << "Different Memory Address\n"; } // Updating a static member updates it for // all instances of the class A.Armor = 0.5; std::cout << B.Armor; }
Same Memory Address
Different Memory Address
0.5

Because of this, static members do not need to be accessed in the context of any specific member. As a result, they will not have access to the this pointer, and cannot directly use any other class members unless those members are also static.

Conceptually, static members are very similar to free functions and variables, with the class acting like a namespace:

#include <iostream>

class Character {/*...*/}; int main() { // Creating an object is unnecessary - we can // directly access static members from the // class using the :: operator std::cout << Character::Armor << '\n'; std::cout << Character::GetArmor(); }
0.2
0.2

Pointers to static members work in the exact same way and have the same type as pointers to free functions and variables:

struct ExampleType{
  static int Add(int x, int y) {
    return x + y;
  }
  
  static int Value;
};

int ExampleType::Value = 42;

int Add(int x, int y) {
  return x + y;
}

int main() {
  int LocalVariable{50};

  int* PointerA{&LocalVariable};
  int* PointerB{&ExampleType::Value};

  int (*PointerC)(int x, int y){
    Add
  };

  int (*PointerD)(int x, int y){
    &ExampleType::Add
  };
}

We cover static members in more detail in a dedicated lesson later in the course.

Pointers to Member Functions

Pointers to non-static member functions are a little more complex, as these functions must be called in the context of some instance of that class. Accordingly, their types are more complex, as they include the name of that class:

#include <string>

class Character {/*...*/}; int main() { // Returns a std::string and accepts no args std::string (Character::*Getter)(){ &Character::GetName}; // Returns void and accepts std::string arg void (Character::*Setter)(const std::string&){ &Character::SetName}; }

We can introduce type aliases to make this syntax friendlier if desired:

#include <string>

class Character {/*...*/}; int main() { using GetNamePtrType = std::string (Character::*)(); using SetNamePtrType = void (Character::*)(const std::string&); GetNamePtrType Getter{&Character::GetName}; SetNamePtrType Setter{&Character::SetName}; }

The standard library includes a variety of helpers to make working with pointers to member functions easier. We'll cover these later in this lesson.

Pointer-to-Member Operators

Invoking a member function through a pointer to it is more complex than invocing free function pointers, functors, or lambdas. That is because, if the function we're pointing at isn't static, it needs to be invoked in the context of some member of that class.

As we've seen, when we know what we want to invoke, we provide the context object using the . or -> operator:

#include <string>

class Character {/*...*/}; int main() { // Call GetName() in the context of PlayerOne Character PlayerOne; PlayerOne.GetName(); // Call GetName() in the context of PlayerTwo Character PlayerTwo; PlayerTwo.GetName(); // Call SetName() in the context of PlayerTwo Character* Ptr{&PlayerTwo}; Ptr->SetName("Anna"); }

When the thing we want to invoke is defined by a pointer to a member, C++ includes two operators, commonly called the pointer-to-member operators, to let us provide that context object.

The .* Operator

With the .* operator, we provide the context object as the left operand, and the member function pointer as the right. For example, if we have a member function pointer called Pointer, and our context object is called Object, our syntax would be Object.*Pointer.

We then invoke what is returned by this operation using the () operator as normal. Note however that the () operator has higher precedence than the .* operator. To ensure the .* operation happens first, we need to introduce an extra set of parenthesis, giving (Object.*Pointer)().

Here's an example:

#include <iostream>

class Character {/*...*/}; int main() { using GetNamePtrType = std::string (Character::*)(); GetNamePtrType GetName{&Character::GetName}; Character PlayerOne{"Anna"}; std::cout << (PlayerOne.*GetName)(); }
Anna

If our method requires arguments, we pass them within the () operator as normal:

#include <iostream>

class Character {/*...*/}; int main() { using SetNamePtrType = void (Character::*)(const std::string&); SetNamePtrType SetName{&Character::SetName}; Character PlayerOne{"Anna"}; (PlayerOne.*SetName)("Roderick"); std::cout << PlayerOne.GetName(); }
Roderick

The ->* Operator

The .* operator accepts a context object as the left operand, but what if we only have a pointer to the context object? We could dereference it in the normal way, using the * operator:

#include <iostream>

class Character {/*...*/}; int main() { using GetNamePtrType = std::string (Character::*)(); using SetNamePtrType = void (Character::*)(const std::string&); GetNamePtrType GetName{&Character::GetName}; SetNamePtrType SetName{&Character::SetName}; Character PlayerOne{"Anna"}; Character* PlayerPtr{&PlayerOne}; std::cout << (*PlayerPtr.*GetName)() << '\n'; (*PlayerPtr.*SetName)("Roderick"); std::cout << (*PlayerPtr.*GetName)(); }
Anna
Roderick

Alternatively, C++ provides the ->* operator, allowing us to combine the * and .* operations into a single step. We provide the pointer to the context object as the left operand and the member function pointer as the right:

#include <iostream>

class Character {/*...*/}; int main() { using GetNamePtrType = std::string (Character::*)(); using SetNamePtrType = void (Character::*)(const std::string&); GetNamePtrType GetName{&Character::GetName}; SetNamePtrType SetName{&Character::SetName}; Character PlayerOne{"Anna"}; Character* PlayerPtr{&PlayerOne}; std::cout << (PlayerPtr->*GetName)() << '\n'; (PlayerPtr->*SetName)("Roderick"); std::cout << (PlayerPtr->*GetName)(); }
Anna
Roderick

The std::invoke() Function

The std::invoke() was added to the standard library in C++17, giving us an alternative way to call functions:

int Add(int x, int y) {
  return x + y;
}

int main() {
  Add(1, 2);
  
  // Equivalently:
  std::invoke(Add, 1, 2);
}

One of the benefits of std::invoke() is that it provides a standard way to execute callables, regardless of the callable type. We've seen how invoking a lambda, for example, uses the basic () syntax, whilst a member function requires the ->* or .* operator.

This is a problem as, when we're writing a function that receives a callable as an argument, we typically want to support any type of callable:

#include <iostream>

class Player {
public:
  std::string GetName() { return Name; }
  std::string Name;
};

void Log(auto Getter, Player& Object) {
  // Incompatible with member function pointers
  std::cout << Getter(Object) << '\n';

  // Incompatible with functors, lambdas, etc
  std::cout << (Object.*Getter)() << '\n';
}

int main() {
  Player PlayerOne{"Anna"};
  Log(&Player::GetName, PlayerOne);
  Log(
    [](Player& P){ return P.Name; },
    PlayerOne
  );
}
error: term does not evaluate to a function taking 1 argument
error: '.*': not valid as right operand

We could work around that using compile-time techniques like assessing type traits, but std::invoke() takes care of that for us.

To invoke a non-static member function using std::invoke, we supply the context object as the second argument. std::invoke can receive this by reference or pointer, including smart pointer:

#include <iostream>

class Player {
 public:
  std::string GetName() { return Name; }
  std::string Name;
};

int main() {
  Player PlayerOne{"Anna"};
  auto Ptr{&Player::GetName};

  std::cout << std::invoke(Ptr, PlayerOne) << '\n';
  std::cout << std::invoke(Ptr, &PlayerOne);
}
Anna
Anna

If our member function requires arguments, we provide them after the context object:

#include <iostream>

class Player {
 public:
  void SetName(std::string NewName, bool Log) {
    Name = NewName;
    if (Log) {
      std::cout << "Name is now " << Name;
    }
  }

  std::string Name;
};

int main() {
  Player PlayerOne;
  auto Ptr{&Player::SetName};

  std::invoke(Ptr, PlayerOne, "Anna", true);
}
Name is now Anna

In the following example, we've written Log() using std::invoke(), meaning it is now compatible with both lambdas and member function pointers:

#include <iostream>

class Player {
public:
  std::string GetName() { return Name; }

  std::string Name;
};

void Log(auto Getter, Player& Object) {
  std::cout << std::invoke(Getter, Object) << '\n';
}

int main() {
  Player PlayerOne{"Anna"};
  Log(&Player::GetName, PlayerOne);
  Log(
    [](Player& P){ return P.Name; },
    PlayerOne
  );
}
Anna
Anna

The std::mem_fn() Function

The standard library's <functional> header includes std::mem_fn(). This function accepts a member function pointer and returns a lightweight wrapper. This wrapper allows us to use that member pointer like a regular function, using the basic () operator.

When invoking the functor returned by std::mem_fn(), we provide the context object as the first argument:

#include <functional>
#include <iostream>

class Player {
public:
  std::string GetName() { return Name; }

  std::string Name;
};

int main() {
  Player PlayerOne{"Anna"};

  auto GetName{std::mem_fn(&Player::GetName)};

  std::cout << GetName(PlayerOne);
}
Anna

If our member function requires additional arguments, we provide them after the context object:

#include <functional>
#include <iostream>

class Player {
public:
  std::string GetName() { return Name; }
 void SetName(const std::string& NewName) {
    Name = NewName;
  }

  std::string Name;
};

int main() {
  Player PlayerOne;

  auto SetName{std::mem_fn(&Player::SetName)};
  SetName(PlayerOne, "Anna");

  std::cout << PlayerOne.GetName();
}

This utility is primarily useful if we need to provide a callable to an API that doesn't directly support member function pointers, using a technique like std::invoke, and we can't update it.

We could handle this incompatibility by introducing an intermediate lambda, for example, but std::mem_fn() takes care of that for us:

#include <functional>
#include <iostream>

class Player {
 public:
  std::string GetName() { return Name; }

  std::string Name;
};

void Log(auto Getter, Player& Object) {
  // Not compatible with member pointers
  std::cout << Getter(Object) << '\n';  
}

int main() {
  Player PlayerOne{"Anna"};

  // Intermediate lambda for compatibility:
  Log([](const Player& P){
    return P.Name;
  }, PlayerOne);

  // Alternative using std::mem_fn():
  Log(std::mem_fn(&Player::GetName), PlayerOne);
}
Anna
Anna

Access Restrictions

Creating a pointer to a member function is controlled by member access specifiers like private and protected in the way we might expect.

In the following example, PrivateMethod is private, so our main function cannot create a pointer to it:

#include <iostream>

class Player {
  void PrivateMethod() {
    std::cout << "Invoking Private Method";
  }
};

int main() {
  auto Ptr{&Player::PrivateMethod};
}
error: 'Player::PrivateMethod': cannot access private member declared in class 'Player'

However, member function pointers provide some additional flexibility here. We can provide access to private or protected functions through less restrictive methods:

class Player {
 public:
  static auto GetPtr() {
    return &Player::PrivateMethod;
  }

 private:
  void PrivateMethod() {
    std::cout << "Invoking Private Method";
  }
};

Once we have a pointer to a private or protected function, we can pass it around and invoke the function without any restrictions:

#include <iostream>

class Player {
 public:
  static auto GetPtr() {
    return &Player::PrivateMethod;
  }

 private:
  void PrivateMethod() {
    std::cout << "Invoking Private Method";
  }
};

int main() {
  auto Ptr{Player::GetPtr()};

  Player PlayerOne;
  std::invoke(Ptr, &PlayerOne);
}
Invoking Private Method

Pointers to virtual Methods

Member function pointers interact with virtual functions in the way we might expect. Below, our Combat function accepts a reference to a Character, and then calls the Attack() method.

We pass a Dragon to this function but, because Character::Attack() is not virtual, our invocation is bound at compile time. Because Attacker is a Character& within the Combat() function, Character::Attack() will be used:

#include <iostream>

class Character {
 public:
  void Attack() {
    std::cout << "Using Character Attack";
  }
};

class Dragon : public Character {
 public:
  void Attack() {
    std::cout << "Using Dragon Attack";
  }
};

void Combat(auto Function, Character& Attacker) {
  std::invoke(Function, Attacker);
}

int main() {
  Dragon Monster;
  Combat(&Character::Attack, Monster);
}
Using Character Attack

If we update Character::Attack() to be virtual (and optionally update Dragon::Attack() to be an override), our invocation of Attacker.Attack() is bound at run time.

Now, our Combat(), function does additional work to determine the specific subtype the Attacker has. It is a Dragon in this example, so Dragon::Attack() will be used:

#include <iostream>

class Character {
 public:
  virtual void Attack() {
    std::cout << "Using Character Attack";
  }
};

class Dragon : public Character {
 public:
  void Attack() override {
    std::cout << "Using Dragon Attack";
  }
};

// Unchanged
void Combat(auto Function, Character& Attacker) {
  std::invoke(Function, Attacker);
}

// Unchanged
int main() {
  Dragon Monster;
  Combat(&Character::Attack, Monster);
}
Using Dragon Attack

Using const with Member Function Pointers

As with most other types of pointer, the const keyword can be used in two ways:

  • The pointer is const
  • The function the pointer is pointing at is const

This creates four possible combinations. The first we've already seen - in previous examples, neither the pointer nor the function being pointed at was const. Let's see examples of the three other possibilities.

const Pointer

A const variable that stores a member function pointer works in the same way as any other const variable. Once the variable is initialized, we can't update it to point to a different function:

#include <iostream>

class Character {
 public:
  std::string GetName() const {
    return Name;
  }

  void SetName(const std::string& NewName) {
    Name = NewName;
  }

  std::string Name;
};

int main() {
  using SetNamePtrType =
    void (Character::*)(const std::string&);

  Character PlayerOne;

  // Creating a const pointer
  const SetNamePtrType SetName{
    &Character::SetName};

  // This is fine
  (PlayerOne.*SetName)("Anna");  

  // This is not
  SetName = nullptr;  
}
error: 'SetName': you cannot assign to a variable that is const

Pointer to const

When a member function is const, that const-ness will also be reflected in the type of any variable that stores a pointer to it:

#include <iostream>

class Character {
 public:
  std::string GetName() const {
    return Name;
  }

  std::string Name;
};

int main() {
  using GetNamePtrType =
    std::string (Character::*)() const;

  GetNamePtrType Func{&Character::GetName};
}

If an instance of our class is const, any member function pointer we're invoking on it must also be a pointer-to-const:

#include <iostream>

class Character {
 public:
  std::string GetName() const {
    return Name;
  }

  void SetName(const std::string& NewName) {
    Name = NewName;
  }

  std::string Name;
};

int main() {
  using GetNamePtrType =
    std::string (Character::*)() const;

  using SetNamePtrType =
    void (Character::*)(const std::string&);

  GetNamePtrType GetName{&Character::GetName};
  SetNamePtrType SetName{&Character::SetName};

  // Creating a const Character
  const Character PlayerOne; 

  // GetName is a pointer to const
  // so this is fine
  (PlayerOne.*GetName)();

  // SetName is a pointer to non-const
  // so this will generate a compilation error
  (PlayerOne.*SetName)("Roderick");

  // SetName itself is not const
  // so this is fine:
  SetName = nullptr;
}
error: 'argument': cannot convert from 'const Character' to 'Character &'

const Pointer to const

Finally, we can use const in both contexts, giving us a const pointer to const:

#include <iostream>

class Character {
 public:
  std::string GetName() const {
    return Name;
  }

  void SetName(const std::string& NewName) {
    Name = NewName;
  }

  std::string Name;
};

int main() {
  using GetNamePtrType =
    std::string (Character::*)() const;


  const GetNamePtrType GetName{&Character::GetName};

  SetName = nullptr;
}
error: 'GetName': you cannot assign to a variable that is const

Pointers to Data Members

We're not restricted to creating pointers to member functions - we point to data members in the same way:

#include <iostream>

class Player {
 public:
  int GetScore() {
    return Score;
  }

  int Score;
};

int main() {
  Player John{50};

  // Pointer to Function Member
  int (Player::*FunctionPtr)(){&Player::GetScore};
  std::cout << "Function Member: "
    <<(John.*FunctionPtr)();

  // Pointer to Data Member
  int Player::*DataPtr{&Player::Score};
  std::cout << "\nData Member using .* "
    << John.*DataPtr;

  Player* JohnPtr{&John};
  std::cout << "\nData Member using ->* "
    << JohnPtr->*DataPtr;

  // Updating Data Member through Pointer:
  std::cout << "\nUpdating Data Member using .* "
    << (John.*DataPtr += 50);

  std::cout << "\nUpdating Data Member using ->* "
    << (JohnPtr->*DataPtr += 50);
}
Function Member: 50
Data Member using .* 50
Data Member using ->* 50
Updating Data Member using .* 100
Updating Data Member using ->* 150

An additional benefit of the std::invoke() helper is that the first argument can be a pointer to a data member, giving us even more flexibility:

#include <iostream>

class Player {
 public:
  std::string Name;
};

void Log(auto Getter, Player& Object) {
  std::cout << std::invoke(Getter, Object) << '\n';
}

int main() {
  Player PlayerOne{"Anna"};

  // We can use a lambda:
  Log([](Player& P){
    return P.Name;
  }, PlayerOne);

  // Or a simple pointer-to-member:
  Log(&Player::Name, PlayerOne);
}
Anna
Anna

This is true of many other standard library utilities, including std::mem_fn() and binding functions such as std::bind_front():

#include <iostream>
#include <functional>

class Player {
 public:
  int Score;
};

int main() {
  Player John(50);
  int Player::*Ptr{&Player::Score};

  std::cout << "Using std::invoke: "
    << std::invoke(Ptr, &John) << '\n';

  auto GetPlayerScore{std::mem_fn(Ptr)};
  std::cout << "Using std::mem_fn: "
    << GetPlayerScore(&John) << '\n';

  auto GetJohnScore{std::bind_front(Ptr, &John)};
  std::cout << "Using std::bind_front: "
    << GetJohnScore();
}
Using std::invoke: 50
Using std::mem_fn: 50
Using std::bind_front: 50

Pointers to Static Data Members

Pointers to static data members are much simpler, behaving exactly like pointers to any other variable:

#include <iostream>

class Player {
 public:
  static int Health;
};

int Player::Health{100};

int main() {
  int* PlayerHealth{&Player::Health};
  std::cout << *PlayerHealth;
}
100

Summary

In this lesson, we've expanded our knowledge of function pointers to include member function pointers. Here are the key takeaways:

  • Static member function pointers behave similarly to free function pointers and are useful for global game functions.
  • Non-static member function pointers require an object instance to be called and represent object-specific behaviors.
  • The .* and ->* operators are used to invoke member functions through pointers.
  • std::invoke provides a modern, uniform way to call any callable object, including member function pointers.
  • std::mem_fn() wraps member function pointers for use in generic code and algorithms.
  • std::function can simplify the typing of complex member function pointers and provide a uniform interface for different types of callables.
  • Member function pointers can have const qualifiers, affecting how they can be used.
Next Lesson
Lesson 69 of 128

Function Binding and Partial Application

This lesson covers function binding and partial application using std::bind(), std::bind_front(), std::bind_back() and std::placeholders.

Questions & Answers

Answers are generated by AI models and may not have been reviewed. Be mindful when running any code on your device.

Member Function Pointers vs Virtual Functions
Why would we use member function pointers instead of virtual functions when we want to change behavior at runtime?
Member Function Pointers with Templates
Can we use member function pointers with templates to create generic callbacks that work with any class that has a specific method signature?
Member Function Pointer Performance
How do member function pointers impact performance compared to virtual functions or std::function? Are there memory overhead considerations?
Async Member Function Pointers
How can we use member function pointers with asynchronous operations like std::async or thread pools?
Serializing Member Function Pointers
What's the best way to serialize/deserialize member function pointers for saving game state or network communication?
Lambda Member Function Pointers
Is it possible to create a member function pointer to a lambda that's stored as a class member?
Smart Pointers with Member Functions
How can we use member function pointers with smart pointers (shared_ptr, unique_ptr) effectively?
Or Ask your Own Question
Get an immediate answer to your specific question using our AI assistant