Safe Non Owning Pointers
Non Owning C++ Pointer Types
One of the biggest issues that arise in C++ is who owns a particular pointer.
For example consider the simple function declaration below.
int* function();
The caller of this function does not know at first glance whether then need to call delete on the pointer or whether they are just borrowing the pointer and library will take care of releasing the resource. If the caller deletes the resource when they are not supposed to bad things will happen most likely and we will run into the land of undefined behaviour. If we were supposed to call delete then we have leaked the resource. The only thing we can really hope for is either the documentation is good or we can read the source to determine the right course of action.
If this leaves you feeling pretty unsatisfied read on. We can help improve on this by making this
explicit in our own code. Enter observable_ptr
Before we jump in for those who have not overloaded the * (dereference) and -> (arrow) operators this will be explained in depth as we go.
#include <cassert>
template<typename T>
class observable_ptr {
public:
friend bool operator==(observable_ptr<T> const& lhs,
observable_ptr<T> const& rhs);
friend bool operator!=(observable_ptr<T> const& lhs,
observable_ptr<T> const& rhs);
explicit observable_ptr(T* ptr = nullptr)
: ptr_{ptr}
{
}
observable_ptr(observable_ptr const&) = default;
observable_ptr& operator=(observable_ptr const&) = default;
T& operator*() noexcept;
T const& operator*() const noexcept;
T* operator->() noexcept;
T const* operator->() const noexcept;
bool empty() const noexcept;
explicit operator bool() const noexcept;
~observable_ptr() = default;
private:
T* ptr_{}; // default ptr to nullptr
};
template<typename T>
bool operator==(observable_ptr<T> const& lhs, observable_ptr<T> const& rhs) {
return lhs.ptr_ == rhs.ptr_;
}
template<typename T>
bool operator!=(observable_ptr<T> const& lhs, observable_ptr<T> const& rhs) {
return !(lhs == rhs);
}
template<typename T>
T& observable_ptr<T>::operator*() noexcept {
assert(ptr_ != nullptr);
return *ptr_;
}
template<typename T>
T const& observable_ptr<T>::operator*() const noexcept {
assert(ptr_ != nullptr);
return *ptr_;
}
template<typename T>
T* observable_ptr<T>::operator->() noexcept {
assert(ptr_ != nullptr);
return ptr_;
}
template<typename T>
T const* observable_ptr<T>::operator->() const noexcept {
assert(ptr_ != nullptr);
return ptr_;
}
template<typename T>
bool observable_ptr<T>::empty() const noexcept {
return ptr_ == nullptr;
}
template<typename T>
observable_ptr<T>::operator bool() const noexcept {
return ptr_ != nullptr;
}
Let me know what you think of this article in the comment section below!