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
.
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
};
}
std::shared_ptr
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
}
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:
Choose the approach that best matches your needs:
unique_ptr
for single-owner scenariosshared_ptr
when sharing observers between componentsAnswers to questions are automatically generated and may not have been reviewed.
An overview of the options we have for building flexible notification systems between game components