Delegates and the Observer Pattern

Smart Pointers with Observers

Can I use smart pointers to automatically manage observer registration/unregistration?

Abstract art representing programming

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

Answers to questions are automatically generated and may not have been reviewed.

sdl2-promo.jpg
Part of the course:

Game Dev with SDL2

Learn C++ and SDL development by creating hands on, practical projects inspired by classic retro games

Free, unlimited access

This course includes:

  • 67 Lessons
  • 100+ Code Samples
  • 91% Positive Reviews
  • Regularly Updated
  • Help and FAQ
Free, Unlimited Access

Professional C++

Comprehensive course covering advanced concepts, and how to use them on large-scale projects.

Screenshot from Warhammer: Total War
Screenshot from Tomb Raider
Screenshot from Jedi: Fallen Order
Contact|Privacy Policy|Terms of Use
Copyright © 2024 - All Rights Reserved