5
\$\begingroup\$

WeakPtr can point to any object provided it inherits from WeakBase. So, for example, you could use it to observe an object managed by a unique_ptr (that's my use case). It is not possible to make this thread safe.

Curious what could be improved.

#ifndef WeakPtr_h
#define WeakPtr_h

#include <memory>
#include <iostream>
#include <cassert>

class WeakBase;

struct Remnant {
  bool alive = true;
};

// An intrusive zeroing weak pointer.
// Not thread safe.
template<class T>
class WeakPtr {

  std::shared_ptr<Remnant> _r;
  T* _p;

 public:
  WeakPtr(T* ptr = nullptr) : _r(ptr ? ptr->GetRemnant() : nullptr ), _p(ptr) { }

  WeakPtr(const WeakPtr& other) : _r(other._r), _p(other._p) { }

  ~WeakPtr() { }

  WeakPtr& operator=(const WeakPtr& other) {
    if(this != &other) {
      _r = other._r;
      _p = other._p;
    }
    return *this;
  }

  T* operator->() const { 
    assert(_r);
    assert(_r->alive);
    return _p;
  }

  operator bool() const { 
    if(_r) {
      return _r->alive; 
    }
    return false;
  }

  T* Get() const {
    if(_r && _r->alive)
      return _p;
    return nullptr;
  }

  std::shared_ptr<Remnant> GetRemnant() const {
    return _r;
  }

  T& operator*() const {
    assert(_r);
    assert(_r->alive);
    return *Get();
  }

  template<class T2>
  bool operator==(const WeakPtr<T2>& other) const {
    return _r == other.GetRemnant();
  }

  template<class T2>
  bool operator!=(const WeakPtr<T2>& other) const {
    return _r != other.GetRemnant();
  }

  template<class T2>
  bool operator<(const WeakPtr<T2>& other) const {
    // Compare remnants to ensure the order
    // is stable even after objects are deleted.
    return _r < other.GetRemnant();
  }

  void Print(std::ostream& out) const {
    out << _p;
  }

}; // class WeakPtr

class WeakBase {

  std::shared_ptr<Remnant> _r;

public:

  WeakBase() { }

  const std::shared_ptr<Remnant>& GetRemnant() {

    if(! _r)
      _r = std::make_shared<Remnant>();
    return _r;
  }

  virtual ~WeakBase() {
    if(_r)
      _r->alive = false;
  }

};

template<class T>
std::ostream& operator<<(std::ostream& out, const WeakPtr<T>& p) {
  p.Print(out);
  return out;
}

#endif // WeakPtr_hpp

Commentary

It would be possible to eliminate Remnant and reduce the size of WeakPtr by using the null_deleter technique here: http://www.boost.org/doc/libs/1_59_0/libs/smart_ptr/sp_techniques.html#weak_without_shared. However that results in needless atomic modification of the shared_ptr use count (via weak_ptr::lock) every time the pointer is used.

Basic Usage Example

struct TestWeakPtr : public WeakBase {

  virtual ~TestWeakPtr() { }

};

static void basicTest() {
  auto p = new TestWeakPtr;

  WeakPtr<TestWeakPtr> wp(p);

  assert(wp);

  delete p;

  assert(! wp);
}
\$\endgroup\$
  • 2
    \$\begingroup\$ But your using std::shared_ptr on the hood. Why add another layer on top of that. The point of having this type of class would be to reduce overhead of one of the standard standard smart pointers (but you don't achieve this). \$\endgroup\$ – Martin York Jul 24 '16 at 18:49
  • \$\begingroup\$ Some more tips on smart pointer can be found here: lokiastari.com/blog/2014/12/30/… \$\endgroup\$ – Martin York Jul 24 '16 at 18:51
  • \$\begingroup\$ @LokiAstari No, the point is to have a smart pointer which can observe an object managed by a unique_ptr, not to reduce overhead. \$\endgroup\$ – Taylor Jul 24 '16 at 19:04
  • \$\begingroup\$ Please provide a usage example stackoverflow.com/help/mcve \$\endgroup\$ – buld0zzr Jul 25 '16 at 10:16

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.