Pointers to Members

Learn how to create pointers to class functions and data members, and how to use them
This lesson is part of the course:

Professional C++

Comprehensive course covering advanced concepts, and how to use them on large-scale projects.

Free, Unlimited Access
Abstract art representing computer programming
Ryan McCombe
Ryan McCombe
Posted

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

std::invoke()

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

std::is_member_pointer

Before C++17, or if we need to determine whether a type is a member pointer anyway, the standard library includes the std::is_member_pointer type trait to help us:

#include <iostream>
#include <type_traits>

class Player {/*...*/}; template <typename T> void Log(T Getter, Player& Object) { if constexpr (std::is_member_pointer_v<T>) { std::cout << "Member pointer: " << (Object.*Getter)() << '\n'; } else { std::cout << "Not a member pointer: " << Getter(Object) << '\n'; } } int main() { Player PlayerOne{"Anna"}; Log(&Player::GetName, PlayerOne); Log([](Player& P){ return P.Name; }, PlayerOne); }
Member pointer: Anna
Not a member pointer: Anna

std::mem_fn()

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

Preview: Partial Application

In real-world applications, it’s quite rare for us to provide both a member function pointer and a context object to pass as the first argument.

Most real-world APIs will simply require a function they can call without needing to forward any consumer-provided arguments. To solve this, functional programming includes the notion of partial application, which allows us to provide only some of the required arguments.

This is sometimes referred to as binding, and the binding process returns a new callable object. This callable can then be invoked by passing only the remaining arguments, or no arguments at all if we bound everything that was required.

Below, we use std::bind() to provide the context object to our Player::GetName() function. The object returned by std::bind() is then passed to the Log() function.

Player::GetName() has no further parameters, so Log() can invoke the bound object without needing to provide any arguments:

#include <functional> // For std::bind 
#include <iostream>

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

void Log(auto Getter) {
  std::cout << Getter();  
}

int main() {
  Player PlayerOne{"Anna"};
  Log(std::bind(&Player::GetName, &PlayerOne));
}
Anna

We cover function binding in detail in the next lesson.

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

const

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.

Was this lesson useful?

Next Lesson

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.
Abstract art representing computer programming
Ryan McCombe
Ryan McCombe
Posted
A computer programmer
This lesson is part of the course:

Professional C++

Comprehensive course covering advanced concepts, and how to use them on large-scale projects.

Free, Unlimited Access
A computer programmer
This lesson is part of the course:

Professional C++

Comprehensive course covering advanced concepts, and how to use them on large-scale projects.

Free, unlimited access

This course includes:

  • 125 Lessons
  • 550+ Code Samples
  • 96% Positive Reviews
  • Regularly Updated
  • Help and FAQ
Next Lesson

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.
Abstract art representing computer programming
Contact|Privacy Policy|Terms of Use
Copyright © 2024 - All Rights Reserved