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:
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