0

As I understand C++ Core Guidelines by Bjarne Stroustrup & Herb Sutter, it is a good idea to use pointers this way:

  • unique_ptr for the good old clear one ownership
  • shared_ptr for truly shared ownership by design
  • weak_ptr when by design the pointer might not be valid anymore
  • raw pointer whenever the pointer is valid by design, and ownership is unchanged

Thus theoretically it still may be a common bug that a raw pointer becomes invalid (see R37).
I wonder if there is a way for debug/QA purposes, to make an assertion of the validity of the raw pointer. I believe such checks can help improve stability of programs dramatically.

I have written the following possible implementation. My question:
What do you think about having such code? Does it make sense to you? Does something like this exist? Any other comments?

At this stage incompleteness of the implementation is not so important (thread safety or other stuff), unless it reflects a reason for not doing it.

#include <memory>
#include <cassert>

#if NDEBUG

template <typename T>
using WatchedPtr = T*;

template <typename T>
T* get (const std::shared_ptr<T>& p) {return p.get();}

#else

template <typename T>
class WatchedPtr {
    template <typename T1>
    friend WatchedPtr<T1> get (const std::shared_ptr<T1>& p);

public:
    WatchedPtr() : _ptr (nullptr) {}

    operator T* () { return getPtr();}

    T* operator->() const { assert (getPtr()); return getPtr(); }

    T& operator*() const { assert (getPtr()); return *getPtr(); }

private:
    T* getPtr () const { return _weak.expired () ? nullptr : _ptr;}
    WatchedPtr(std::shared_ptr<T> p) : _weak (p) {
        _ptr = _weak.lock().get();
    }
    std::weak_ptr<T> _weak;
    T* _ptr;
};

template <typename T1>
inline WatchedPtr<T1> get (const std::shared_ptr<T1>& p) {return WatchedPtr<T1> (p);} //because I cannot change the get() member

#endif

Usage example:

void f (WatchedPtr<int> p) {
    assert(p);//detects errors
    *p = 3;//detects errors
    std::cout << "f: *p=" << *p << std::endl;
}

void g (int* p) {
    assert(p);//detects errors
    *p = 3;//does not detect error
    std::cout << "g: *p=" << *p << std::endl;
}

int main () {
    WatchedPtr<int> p;

    {
        auto pp = std::make_shared<int> (1234);
        p = get(pp);
        f (p);
    }
    f (p);
    g (p); //the internal assert() of g() will detect errors, because casting of invalid p to raw pointer will be nullptr
}

*I thought of implementing also for unique_ptr. But so far my only idea is to store a pointer to unique_ptr in WatchedPtr. This will identify errors only on some of the cases, where get() or bool operator will crash or return nullptr.

Doron Ben-Ari
  • 443
  • 3
  • 12
  • Probably better on https://codereview.stackexchange.com/ unless you have a specific _"not working"_ type of question. Please read [ask]. – Richard Critten May 11 '19 at 20:06
  • @RichardCritten The correctness of the implementation is not the important part at the moment. I updated the title. Thanks. – Doron Ben-Ari May 11 '19 at 20:11
  • There are two things I like about this idea: (1) it will help you find bugs more easily in your debug builds, and (2) it doesn't cost you any efficiency in your release builds. The only potential downside I see is that it might be difficult in some cases to integrate this pointer-type with third-party APIs that are expecting other types of pointers in their method/function arguments, but I think that is a minor concern. – Jeremy Friesner May 12 '19 at 17:34
  • `get` is too generic of a name, and I think it suspicious that `getPtr()` returns `null` instead of crashing. `assert` doesn't crash in release builds. And you don't need `_ptr` since you have `_weak`. But that's all code review. – Mooing Duck May 13 '19 at 06:30

0 Answers0