Smart Pointers with Member Functions

How can we use member function pointers with smart pointers (shared_ptr, unique_ptr) effectively?

Member function pointers can be used effectively with smart pointers, but require some special consideration. Here's how to handle different scenarios:

Basic Usage with std::shared_ptr

Here's a minimalist example using std::shared_ptr:

#include <iostream>
#include <memory>

class Character {
public:
  void Attack() {
    std::cout << "Character attacks!\n";
  }

  void TakeDamage(int Amount) {
    Health -= Amount;
    std::cout << "Health: " << Health << '\n';
  }

private:
  int Health{100};
};

int main() {
  auto player = std::make_shared<Character>();

  // Store member function pointers
  void (Character::*attackPtr)() =
    &Character::Attack;
  void (Character::*damagePtr)(int) =
    &Character::TakeDamage;

  // Call through shared_ptr
  (player.get()->*attackPtr)();
  (player.get()->*damagePtr)(30);
}
Character attacks!
Health: 70

Using std::invoke with Smart Pointers

In this example, we combine std::invoke with smart pointers:

#include <functional>
#include <iostream>
#include <memory>

class Character {
public:
  void Attack() {
    std::cout << "Character attacks!\n";
  }

  void TakeDamage(int Amount) {
    Health -= Amount;
    std::cout << "Health: " << Health << '\n';
  }

private:
  int Health{100};
};

template <
  typename Ptr, typename Func, typename ...Args>
void SafeInvoke(const Ptr& Object,
                Func MemberFunc,
                Args&&... args) {
  if (Object) {
    std::invoke(MemberFunc, Object.get(),
                std::forward<Args>(args)...);
  }
}

int main() {
  auto player = std::make_shared<Character>();

  // Safer invocation with null checking
  SafeInvoke(player, &Character::Attack);
  SafeInvoke(player, &Character::TakeDamage,
             30);

  // Demonstrate null safety
  std::shared_ptr<Character> nullPlayer;
  SafeInvoke(nullPlayer, &Character::Attack);
  // Safely ignored
}
Character attacks!
Health: 70

Callback System with Smart Pointers

We can use these techniques to lay the foundation of an event system:

#include <functional>
#include <iostream>
#include <memory>
#include <vector>

class Character {
public:
  void OnDamage(int Amount) {
    std::cout << "Character took " << Amount <<
      " damage\n";
  }
};

class EventSystem {
public:
  template <typename T>
  void RegisterCallback(
    std::shared_ptr<T> Instance,
    void (T::*Func)(int)) {
    // Store a weak_ptr instead of shared_ptr
    // to prevent circular references
    std::weak_ptr<T> weakInstance = Instance;
    callbacks.push_back(
      [weakInstance, Func](int amount){
        // Try to lock the weak_ptr to get
        // a shared_ptr
        if (auto strongInstance = weakInstance.
          lock()) {
          (strongInstance.get()->*Func)(amount);
        }
      });
  }

  void BroadcastDamage(int Amount) {
    for (const auto& callback : callbacks) {
      callback(Amount);
    }
  }

private:
  std::vector<std::function<void(int)>>
  callbacks;
};

int main() {
  EventSystem events;

  // Create scope to demonstrate
  // lifetime management
  {
    auto player = std::make_shared<Character>();
    events.RegisterCallback(
      player, &Character::OnDamage);
    std::cout << "With valid player:\n";
    events.BroadcastDamage(30);
  }

  std::cout << "After player destroyed:\n";
  events.BroadcastDamage(30); // Safely ignored
}
With valid player:
Character took 30 damage
After player destroyed:

Key considerations:

  • Always check for null pointers
  • Use std::weak_ptr for callbacks to prevent circular references
  • Consider using std::invoke for cleaner syntax
  • Be careful with lifetime management
  • Use type erasure when storing callbacks
  • Consider thread safety with shared resources

Best practices:

#include <iostream>
#include <memory>

class Character {
public:
  void Attack() { std::cout << "Attack!\n"; }
};

// Bad: Raw pointer usage
void UnsafeCall(Character* chr,
                void (Character::*func)()) {
  (chr->*func)(); 
}

// Good: Smart pointer with null check
void SafeCall(
  const std::shared_ptr<Character>& chr,
  void (Character::*func)()) {
  if (chr) { std::invoke(func, chr.get()); }
}

int main() {
  auto player = std::make_shared<Character>();

  // Safe usage
  SafeCall(player, &Character::Attack);

  // Also safe - null check prevents crash
  SafeCall(nullptr, &Character::Attack);
}
Attack!

This approach ensures safe handling of member function pointers with smart pointers while maintaining good performance and clean code.

Pointers to Members

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

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?
Or Ask your Own Question
Get an immediate answer to your specific question using our AI assistant