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
  };
}

Using 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
}

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 scenarios
  • shared_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

Questions & Answers

Answers are generated by AI models and may not have been reviewed. Be mindful when running any code on your device.

Why Use Delegates?
Why do we need to use delegates instead of just calling functions directly?
Delegate Performance Impact
How does the performance of delegates compare to direct function calls?
Handling Multiple Event Types
Can I have an observer observe multiple different types of events from the same subject?
Pausing Observer Notifications
Is it possible to temporarily pause notifications to specific observers?
std::function vs Function Pointer Performance
What's the overhead of using std::function compared to raw function pointers?
Observer Pattern Without Dynamic Allocation
Is it possible to implement the observer pattern without dynamic memory allocation?
Or Ask your Own Question
Get an immediate answer to your specific question using our AI assistant