Sometimes you have to deal with pointers of instances hold outside your responsibility. This can be due to using closed libraries or maintaining software which isn’t well designed but you cannot change it however. I recently got a case where a vector holds class instances directly inside itself (not as shared or unique pointer). This vector wasn’t accessible and the holding class did deliver only pointers to items of this vector:

class Item {};

class Holder {hugo 
private:
    std::vector<A> vec_;
public:
    Item* getItem(void) noexcept {
        return vec_.empty() ? nullptr : vec_.front();
    }
};

Item *pi{};
{
    Holder holder; // lets assume that Holder::vec_ is filled with Item instances
    pi = holder.getItem();
}
// here holder is destroyed and so all Item instances within
if (pi) {
    // problem: pi stills points to something, even if its not valid
}

Lets assume that the storage of Item instances inside the vector isn’t allowed to be changed and the return value from Holder::getItem should still behave like a pointer.

What we need here is some kind of std::weak_ptr without the need of a shared pointer.

To check the validity of such weak pointer, a validator is required. It will be asked by each WeakPointer having the same target if before the target is accessed. This validator should know if its target is invalid as soon as the target gets destroyed. This leads to the next requirement: the target has to tell the validator when its gone. This could be solved by a guard which becomes a member of the target class (T in the following).

s s h W h a e a r a r e k e d G P d _ u o _ p a i p G t T r n t u r a d t r a < r e < r V g g r V d a e u < a l t a T l i r a i d d r d a _ g a t e t o t o r > r > > T a r V g a e l t i * d a o t b o j r e c t _

As seen the Target owns a Guard member which has a shared pointer to Validator. The Validator holds a pointer to Target which is both the target pointer as well as the validity check, more on this later. The WeakPointer also has a shared pointer to Validator.

Most important: the Guard is creator of Validator, not the WeakPointer! The Validator gets created with a pointer to Target by the Guard. As soon as Target gets destroyed, Guard gets destroyed too and must set the pointer to Target in Validator to nullptr.

The WeakPointer gets the pointer to Target only from the Validator. This way the pointer is valid as long as Target exists. As soon as Target gets destroyed the WeakPointer also points to nullptr due to refering to it using the Validator.

A long description, lets look at the implementation. In the interests of simplicity, I have refrained from typing Guard and Validator and use a void* pointer instead in Validator (which is still save at this point due to the used encapsulation):

Validator

class Validator {
private:
  // the pointer to the Target instance
  void* object_ = nullptr;

  // private, because only the Guard is allowed to set (construct) or invalidate the pointer to Target
  void invalidate() noexcept {
    object_ = nullptr;
  }

  Validator(void* instance)
    :object_(instance)
  {
    if (!object_) {
      // it doesn't make sense to watch for null objects  
      throw std::bad_weak_ptr();
    }
  }

public:
  // Validator cannot be moved nor copied:
  Validator(Validator&&) = delete;
  Validator& operator=(Validator&&) = delete;
  Validator(const Validator&) = delete;
  Validator& operator=(const Validator&) = delete;

  // The Guard is our friend, so it can access the constructor and invalidate() method directly.
  friend class Guard;

  operator bool() const noexcept {  return object_ != nullptr;  }

  [[nodiscard]] inline void* ptr() noexcept {  return object_; }
};

By setting both constructor and invalidation method to private, only the friend Guard is allowed to access them. Adding the bool operator allows checking a Validator instance directly for validity.

Guard

The Guard is pretty small and simply owns the std::shared_pointer to its Validator, which is created directly on construction. Note that I don’t use std::make_shared for the Validator creation. This would lead to making the Validator constructor public and so allowing accidential misusage - only the Guard should be allowed creating the Validator instance.

class Guard {
private:
  std::shared_ptr<Validator> validator_;

public:
  // Validator cannot be moved nor copied:
  Guard(Guard&&) = delete;
  Guard& operator=(Guard&&) = delete;
  Guard(const Guard&) = delete;
  Guard& operator=(const Guard&) = delete;

  Guard(void* instance)
    :validator_(new Validator(instance))
  {}

  ~Guard() {
    validator_->invalidate();
  }

  [[nodiscard]] inline std::shared_ptr<Validator> get_validator() const {
    return { validator_ };
  }
};

Why isn’t the Guard allowed beeing copyied or moved? Because the Guard owns the Validator and is responsible for setting the pointer to the instance correctly. Copying or moving the Guard would also copy or move the pointer to Target implicitly, which is not what we want to do here.

Assume we would allow the Guard beeing copyable:

MyClass inst;
WeakPointer<MyClass> wp{&inst};  // here the Guard of inst points to inst
MyClass inst_copy = inst;        // copying Guard means that inst_copy`s Guard now also points to inst - not to inst_copy!

WeakPointer

Here is the most work to do, since instances of this class should behave like a regular pointer.

template < typename T >
class WeakPointer {
private:
  std::shared_ptr<Validator> validator_;
  [[nodiscard]] T* ptr() const noexcept {
    return reinterpret_cast<T*>(validator_ ? validator_->ptr() : nullptr);
  }
public:
  // this allows any kind of WeakPointer class beeing a friend - no matter what its Target T is.
  template <typename U> friend class WeakPointer;
  // allow the right side equality and less than comparison operator beeing defined outside
  template < typename T > friend bool operator==(const T*, const WeakPointer<T>&) noexcept;
  template < typename T > friend bool operator<(const T*, const WeakPointer<T>&) noexcept;
  // allow getting the container pointer
  template < typename T > friend T* weakpointer_cast(WeakPointer<T>&) noexcept;

  // construct directly with a validator - normally used by `T` instances delivering a weak pointer to themselves
  WeakPointer(std::shared_ptr<Validator> validator)
    :validator_(validator)
  {}

  // construct with a pointer to a `Target` instance.
  // This requires that `T` defines a get_validator() method when used!
  WeakPointer(T* instance) {
    if (instance) {
      validator_ = instance->get_validator();
    }
  }

  // this copy constructor allows copying from a WeakPointer containing Target U, where U is either same as T or
  // or derived from it.
  template < typename U, typename = typename std::enable_if< std::is_base_of_v <T, U> || std::is_same_v<T, U> >::type >
  WeakPointer(const WeakPointer<U>& other) {
    validator_ = other.validator_;
  }

  // see copy constructor.
  template < typename U, typename = typename std::enable_if< std::is_base_of_v <T, U> || std::is_same_v<T, U> >::type >
  WeakPointer<T>& operator=(WeakPointer<U> other) {
    validator_ = other.validator_;
    return *this;
  }

  // check for equality with any other WeakPointer by comparing the Target it points to
  template< typename U> 
  inline bool operator==(const WeakPointer<U>& other) const noexcept {
    return ptr() == other.ptr();
  }

  // check if less than any other WeakPointer by comparing the Target it points to
  template< typename U>
  inline bool operator<(const WeakPointer<U>& other) const noexcept {
    return ptr() < other.ptr();
  }

  // since the weak pointer could have a shared pointer to null for the `Validator`, here
  // both the shared pointer to the `Validator` as well as the `Validator` itself (if any) are used for evaluation.
  inline operator bool() const noexcept {
    return validator_ && *validator_;
  }

  inline T* operator->() {
    T* _p = ptr();
    if (!_p) {
      throw std::bad_weak_ptr();
    }
    return _p;
  }

  [[nodiscard]] inline T& operator*() {
    T* _p = ptr();
    if (!_p) {
      throw std::bad_weak_ptr();
    }
    return *_p;
  }

  std::shared_ptr<Validator> get_validator() { return validator_; }
};

// for allowing dynamic casts without extracting the content of the WeakPointer we add this cast function.
// It performs a dynamic cast of the content of WeakPointer<U> to WeakPointer<T>, where U should be a base class of T
// (but not necessarily has to be).
template < typename T, typename U >
[[nodiscard]] WeakPointer<T> dynamic_weakpointer_cast(WeakPointer<U>& src) {
  return { src ? dynamic_cast<T*>(reinterpret_cast<U*>(src.ptr())) : nullptr };
}

// allow equality comparison where Target is left and WeakPointer right:
template < typename T >
bool operator==(const T* elem, const WeakPointer<T>& wp) noexcept {
  return elem == wp.ptr();
}

template < typename T >
bool operator<(const T* elem, const WeakPointer<T>& wp) noexcept {
  return elem < wp.ptr();
}

// get the Target pointer from weak pointer
template < typename T >
[[nodiscard]] T* weakpointer_cast(WeakPointer<T>& src) noexcept {
  return src.ptr();
}

The WeakPointer defines the common used operator as bool operator, arrow operator (T* operator->), the star operator (T& operator*) equality and less than operator which makes it useable like a normal pointer:

MyClass inst;
WeakPointer<MyClass> wp(&inst);
if (wp) {
    WeakPointer<MyClass> wo = wp;
    wp->foo();
    (*wo).bar();
    assert(wo == wp);
    assert(!(wo < wp));
}

Additionally I added two global operators for equality and less than comparison between Target at left and a WeakPointer at right:

MyClass inst;
WeakPointer<MyClass> wp(&inst);
assert(wp == &inst);  // --> WeakPointer<T>::operator==(const T*)
assert(&inst == wp);  // --> operator==(const T*, WeakPointer<T>&)
assert(!wp < &inst);  // --> WeakPointer<T>::operator<(const T*)
assert(!&inst < wp);  // --> operator<(const T*, WeakPointer<T>&)

Target

The target class can be any type of class. The only condition it has to fulfill is having a (optimally private) Guard member which has to be constructed with this and of course a method returning a WeakPointer to it:

class MyClass {
private:
    Guard guard_;
public:
    MyClass():guard_(this) {}
    [[nodiscard]] WeakPointer<MyClass> get_weakpointer() {
        return {guard_};
    }
};

MyClass inst;
WeakPointer<MyClass> wp = inst.get_weakpointer();
assert(wp);

Optionally WeakPointers can be constructed directly with instances of the Target class, in such case a method get_validator is required:

class MyClass {
private:
    Guard guard_;
public:
    MyClass():guard_(this) {}
    [[nodiscard]] std::shared_ptr<Validator> get_validator() { return guard_->get_validator(); }
};

MyClass inst;
WeakPointer<MyClass> wp(&inst);
assert(wp);

Of course the target class can define both methods, but doesn’t have to depending on how it is used.

Important Since neither Guard isn’t move- nor copyable, each class having a Guard member has to explicitly init the Guard member pointing to this:

class MyClass {
private:
    Guard guard_;       // this is not allowed beeing copied to ensure always pointing to its owning instance!
    std::string member; // this can be copied easily
public:
    MyClass():guard_(this) {}
    [[nodiscard]] std::shared_ptr<Validator> get_validator() { return guard_->get_validator(); }

    MyClass(const MyClass& other)
    :guard_(this)
    ,member(other.member)
    {}
};

Usage

Now lets put it all together and do some examples.

# include <string>
using std::operator""s;

// base class having the Guard and both accessor methods.
class A {
public:
  std::string name = "a"s;

  A(void)
    :guard_(this) // this part is important!
  {}

  virtual ~A() {}

  [[nodiscard]] WeakPointer<A> get_weakpointer(void) {
    return { get_validator() };
  }

  [[nodiscard]] std::shared_ptr<Validator> get_validator(void) noexcept {
    return guard_.get_validator();
  }

private:
  Guard guard_;
};

// a simple class derived from A, no need to define additional members related to weak pointer
class B: public A {
public:
  B(const std::string& n) {
    name = n;
  }
};

int main() {

  // create empty (invalid) weak pointer
  WeakPointer<B> pB;
  WeakPointer<A> pA;
  assert(!pB);
  assert(!pA);
  {
    B b("b"s);
    pA = b.get_weakpointer(); // this works because B is inherited from A
    A* pa = &b;               
    pB = dynamic_weakpointer_cast<B>(pA); // this works because `a` points to an instance of B
    assert(pB == pA);
    assert(pA->name == pB->name);
    assert((*pA).name == pB->name);
    assert(pB);  
    assert(pA);
  }
  // scope of B is left, all weak pointers to it have to be invalid now.
  assert(!pB);
  assert(!pA);

  return 0;
}

Not always having the possibility to change something requires creativity. This is not the best solution in performance, due to having the Validator as proxy to the Target, but it increases safety.