Smart Pointers with Observers
Can I use smart pointers to automatically manage observer registration/unregistration?
Yes, smart pointers can help automate observer lifecycle management. Let's explore how to implement this safely using both std::unique_ptr
and std::shared_ptr
.
Using std::unique_ptr
We can create a custom deleter that handles unregistration:
#include <memory>
#include <iostream>
class Player;
class Observer {
public:
Observer(Player& P);
void OnDamage(int NewHealth) {
std::cout << "Health changed to: "
<< NewHealth << '\n';
}
int GetObserverID() const {
return ObserverID;
}
private:
Player& Subject;
int ObserverID;
};
class Player {
public:
void RegisterObserver(Observer* Obs) {
std::cout << "Registering observer "
<< NextID << '\n';
Observers[NextID] = Obs;
NextID++;
}
void UnregisterObserver(int ID) {
std::cout << "Unregistering observer "
<< ID << '\n';
Observers.erase(ID);
}
private:
std::unordered_map<int, Observer*> Observers;
int NextID{0};
};
Observer::Observer(Player& P) : Subject{P} {
Subject.RegisterObserver(this);
}
int main() {
Player P;
// Custom deleter that unregisters the observer
auto Deleter = [&](Observer* Obs) {
if (Obs) {
P.UnregisterObserver(Obs->GetObserverID());
delete Obs;
}
};
// Observer will automatically unregister
// when ptr is destroyed
std::unique_ptr<Observer, decltype(Deleter)> Obs{
new Observer{P}, Deleter
};
}
We can also use std::shared_ptr
with a custom deleter:
class Player {
public:
using ObserverPtr = std::shared_ptr<Observer>;
ObserverPtr CreateObserver() {
return std::shared_ptr<Observer>(
new Observer{*this},
[this](Observer* Obs) {
if (Obs) {
UnregisterObserver(Obs->GetObserverID());
delete Obs;
}
});
}
// ... rest of Player implementation
};
int main() {
Player P;
// Observer created with automatic cleanup
auto Obs = P.CreateObserver();
// Can safely copy the pointer
auto OtherRef = Obs;
// Last reference destroyed = observer unregistered
}
RAII Wrapper
For even more control, we can create a dedicated RAII wrapper:
class ObserverHandle {
public:
ObserverHandle(Player& P)
: Subject{P}, Obs{new Observer{P}} {}
~ObserverHandle() {
if (Obs) {
Subject.UnregisterObserver(
Obs->GetObserverID());
delete Obs;
}
}
// Prevent copying
ObserverHandle(const ObserverHandle&) = delete;
ObserverHandle& operator=(
const ObserverHandle&) = delete;
// Allow moving
ObserverHandle(ObserverHandle&& Other) noexcept
: Subject{Other.Subject}, Obs{Other.Obs} {
Other.Obs = nullptr;
}
private:
Player& Subject;
Observer* Obs;
};
The key benefits of these approaches:
- Automatic cleanup when observers go out of scope
- Exception-safe registration/unregistration
- Clear ownership semantics
- No risk of forgetting to unregister
Choose the approach that best matches your needs:
unique_ptr
for single-owner scenariosshared_ptr
when sharing observers between components- Custom RAII wrapper for more specialized behavior
Delegates and the Observer Pattern
An overview of the options we have for building flexible notification systems between game components