Pointers to Members

Smart Pointers with Member Functions

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

Abstract art representing computer programming

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.

Answers to questions are automatically generated and may not have been reviewed.

A computer programmer
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
Free, Unlimited Access

Professional C++

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

Screenshot from Warhammer: Total War
Screenshot from Tomb Raider
Screenshot from Jedi: Fallen Order
Contact|Privacy Policy|Terms of Use
Copyright © 2024 - All Rights Reserved