Best const ReferenceCountedObjectPtr workflow

Lets say I have classes as follows

class Class : public ReferenceCountedObject
{

public:

    ...

    typedef ReferenceCountedObjectPtr<Class> Ptr;

};


class Data
{

public:

    ...other data...

    Class::Ptr classPtr;

};

I want to do something like this:

1. Construct and fill my Data structure.

2. Pass it to a function. I want to pass it as const to prevent the data being changed.

function (const Data data) { ... }

Passing in const Data (therefore const Class::Ptr doesn't work as ReferenceCountedObjectPtr returns a non-const pointer to the object in a const function.

Is there a good way to const this data structure completely? I could change it to the implementation below, but reformatting all the current code to copy all data into Data in a constructor would be a lot of work.

class Class : public ReferenceCountedObject
{

public:

    ...

    typedef ReferenceCountedObjectPtr<const Class> Ptr;

};

Any ideas?

Yes, this is a common problem with all kinds of smart pointers. I think that in the standard C++ library for things like unique_ptr and shared_ptr, the general consensus is "leave it non-const don't worry about it".

What you'd need here would be some kind of ConstReferenceCountedObjectPtr class, which I've never added, as it's a bit of an edge-case and causes other complications.

If it's really vital to block people from changing it, why not make the data member private, and have an accessor method that returns the target object as a const reference?

I’ve been looking for a solution for this, so I decided to write one. I just made a wrapper around a normal ReferenceCountedObjectPtr and then copied all the methods from the original class. The only difference is that operator* and operator-> return pointers / references to const objects. I also added some new constructors so that you can turn regular ReferenceCountedObjectPtrs into const ones.

I haven’t tested it thoroughly yet, but as far as I can tell, it works as expected. I’d be interested to hear some feedback though. Jules mentioned that there might be complications–can anyone see what these might be?

template <typename T>
class RefCountedConstObjectPtr
{
    using NonConstPtr = ReferenceCountedObjectPtr<T>;

public:

    RefCountedConstObjectPtr() = default;
    RefCountedConstObjectPtr(decltype(nullptr)) noexcept {}
    RefCountedConstObjectPtr(T* rco) noexcept : ptr(rco) {}
    RefCountedConstObjectPtr(T& rco) noexcept : ptr(&rco) {}
    RefCountedConstObjectPtr(const RefCountedConstObjectPtr& other) : ptr(other.ptr) {}
    RefCountedConstObjectPtr(RefCountedConstObjectPtr&& other) : ptr(other.ptr) { other.ptr = nullptr; }
    RefCountedConstObjectPtr(const NonConstPtr& other) : ptr(other) {}
    RefCountedConstObjectPtr(NonConstPtr&& other) : ptr(other) { other = nullptr; }

    RefCountedConstObjectPtr& operator= (const NonConstPtr& other) { return RefCountedConstObjectPtr(ptr.operator= (other)); }
    RefCountedConstObjectPtr& operator= (const RefCountedConstObjectPtr& other) { return RefCountedConstObjectPtr((ptr.operator= (other.ptr))); }

    RefCountedConstObjectPtr& operator= (T* newObject)
    {
        if (newObject != nullptr)
            return operator= ((RefCountedConstObjectPtr) *newObject);

        ptr.reset();
        return *this;
    }

    RefCountedConstObjectPtr& operator= (T& newObject)
    {
        if (ptr.get() != &newObject)
            ptr.operator=(newObject);
    }

    RefCountedConstObjectPtr& operator= (decltype(nullptr))
    {
        ptr.reset();
        return *this;
    }

    RefCountedConstObjectPtr& operator= (NonConstPtr&& other) noexcept
    {
        std::swap(ptr, other);
        return *this;
    }

    RefCountedConstObjectPtr& operator= (RefCountedConstObjectPtr&& other) noexcept
    {
        std::swap(ptr, other.ptr);
        return *this;
    }

    const T* get() const noexcept { return ptr.get(); }
    void reset() noexcept { ptr.reset(); }

        // these two are the important ones
    const T* operator->() const noexcept { return ptr.operator->(); }
    const T& operator*() const noexcept { return ptr.operator*(); }

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

    bool operator == (const T* other) const noexcept { return ptr.get() == other; }
    bool operator == (const NonConstPtr& other) const noexcept { return ptr.get() == other.get(); }
    bool operator == (const RefCountedConstObjectPtr& other) const noexcept { return ptr.get() == other.get(); }

    bool operator != (const T* other) const noexcept { return ptr.get() != other; }
    bool operator != (const NonConstPtr& other) const noexcept { return ptr.get() != other.get(); }
    bool operator != (const RefCountedConstObjectPtr& other) const noexcept { return ptr.get() != other.get(); }

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

    ;
private:
    NonConstPtr ptr = nullptr;
};

Hope this is useful to someone.

Edit: The one thing you can’t do with this is construct it through a const pointer or reference (you’d have to have a const_cast in the constructor, which is potentially unsafe). Then again, const ReferenceCountedObjects won’t compile anyway, so this shouldn’t be too much of a problem.