Home / News / Thoughts on creating a tracking pointer class, part 7: Non-modifying trackers, second try

Thoughts on creating a tracking pointer class, part 7: Non-modifying trackers, second try

Last time, we tried to add non-modifying trackers to our tracking pointers implementation. I noted at the end that our attempt was wrong.

The problem is in the code we didn’t change:

    void set_target(T* p) noexcept
    {
        for (tracking_node* n = m_trackers.next;
             n != &m_trackers; n = n->next) {
            static_cast<tracking_ptr<T>*>(n)->tracked = p;
        }
    }

The static_cast is a downcast from a tracking_node to its derived tracking_ptr<T>. But the derived class might not be tracking_ptr<T>! It could be a tracking_ptr<const T>.

To fix this, we need to use a consistent type for the derived class. We can do this by renaming our original tracking_ptr to tracking_ptr_base, which will serve as the consistent derived class, and then move the get() method to a tracking_ptr that is derived from tracking_ptr_base.

template<typename T>
struct tracking_ptr_base : private tracking_node
{
    // T* get() const { return tracked; }

    tracking_ptr_base() noexcept :
        tracking_node(as_solo{}),
        tracked(nullptr) {}

    tracking_ptr_base(tracking_ptr_base const& other) noexcept :
        tracking_node(copy_node(other)),
        tracked(other.tracked) { }

    ~tracking_ptr_base() = default;

    tracking_ptr_base& operator=(tracking_ptr_base const& other) noexcept {
        tracked = other.tracked;
        if (tracked) {
            join(trackers(tracked));
        } else {
            disconnect();
        }
        return *this;
    }

    tracking_ptr_base& operator=(tracking_ptr_base&& other) noexcept {
        tracked = std::exchange(other.tracked, nullptr);
        tracking_node::displace(other);
        return *this;
    }

private:
    friend struct trackable_object<T>;

    static tracking_node& trackers(T* p) noexcept {
        return p->trackable_object<T>::m_trackers;
    }

    tracking_node copy_node(tracking_ptr_base const& other) noexcept
    {
        if (other.tracked) {
            return tracking_node(as_join{},
                                 trackers(other.tracked));
        } else {
            return tracking_node(as_solo{});
        }
    }

    tracking_ptr_base(T* p) noexcept :
        tracking_node(as_join{}, trackers(p)),
        tracked(p) { }

protected:
    T* tracked;
};

template<typename T>                                        
struct tracking_ptr : tracking_ptr_base<std::remove_cv_t<T>>
{                                                           
public:                                                     
    T* get() const { return this->tracked; }                
                                                            
    using tracking_ptr::tracking_ptr_base::                 
                        tracking_ptr_base;                  
};                                                          

template<typename T>
struct trackable_object
{
    ⟦ ... ⟧

private:
    friend struct tracking_ptr_base<T>;
    // friend struct tracking_ptr<const T>;

    ⟦ ... ⟧

    void set_target(T* p)
    {
        for (tracking_node* n = m_trackers.next;
            n != &m_trackers; n = n->next) {
            static_cast<tracking_ptr_base<T>*>(n)->
                tracked = p;
        }
    }
};

Okay, now we can have an object give away a non-modifying tracking pointer to itself by using ctrack() instead of track().

But wait, this still requires that the original object be itself mutable. But if all you have is a const reference to a trackable object, surely you should be allowed to create a non-modifying tracking pointer to it, right?

We’ll do that next time.

The post Thoughts on creating a tracking pointer class, part 7: Non-modifying trackers, second try appeared first on The Old New Thing.

Tagged: