Member function pointers can be used effectively with smart pointers, but require some special consideration. Here's how to handle different scenarios:
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
std::invoke
with Smart PointersIn 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
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:
std::weak_ptr
for callbacks to prevent circular referencesstd::invoke
for cleaner syntaxBest 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.
Learn how to create pointers to class functions and data members, and how to use them