Search code examples
c++constantsconst-correctness

How to enforce const-correctness regarding pointer data-members


Following a discussion at work, we seem to be unable to enforce "logical" const-correctness on a class which has pointer data-members, as such:

class Widget {
public:
    void Foo();
    void FooConst() const;
};

class WidgetManager {
public:
    WidgetManager() : _pW(std::shared_ptr<Widget>(new Widget())) { }

    void ManagerFoo()
    {
        _pW->Foo();         // should be OK, will not compile if declared as "const Widget*"
        _pW->FooConst();    // should be OK
    }

    void ManagerFooConst() const
    {
        _pW->Foo();         // should NOT be OK, will not compile if declared as "const Widget*"
        _pW->FooConst();    // should be OK
    }

    void RegenerateWidget()
    {
        _pW = std::shared_ptr<Widget>(new Widget());
    }

private:
    std::shared_ptr<Widget> _pW;
};

As can be seen, we would like to have WidgetManager::ManagerFooConst() to not be able to call non-const functions of WidgetManager's pointer members, while still allowing them to be called from other, non-const functions of WidgetManager. This means, declaring the pointer as std::shared_ptr<const Widget> (i.e. const Widget*) is out.

In addition, we would like the option to make the pointer reference another Widget during the manager's life-time, so we don't really want to hold it as a data-member (and can't hold it by reference).

Of course, all "bitwise" const-correctness is enforced here, as no data-members of WidgetManager can be modified from within const methods (including the specific Widget pointed by _pW), but we would like to achieve "logical" const-correctness were even pointed members can't be modified.

The only thing we came-up with is adding a const and non-const "getters of this" to Widget:

class Widget {
public:
    void Foo();
    void FooConst() const;

    Widget* GetPtr()    { return this; }
    const Widget* GetConstPtr() const   { return this; }
};

And reverting to use these instead of the arrow operator directly:

void WidgetManager::ManagerFoo()
{
    // shouldn't use "->" directly (lint?)

    _pW->GetPtr()->Foo();
    _pW->GetPtr()->FooConst();
    //_pW->GetConstPtr()->Foo();        // this won't compile (good)
    _pW->GetConstPtr()->FooConst();

}

void WidgetManager::ManagerFooConst() const
{
    // shouldn't use "->" directly (lint?)

    _pW->GetPtr()->Foo();               // shouldn't be used (lint?)
    _pW->GetPtr()->FooConst();          // shouldn't be used (lint?)
    //_pW->GetConstPtr()->Foo();        // this won't compile (good)
    _pW->GetConstPtr()->FooConst();
}

But this is so ugly, and definitely not enforceable by the compiler.

Specifically, trying to overload operator-> for Widget* and const Widget* didn't seem to change anything: ManagerFooConst() was still able to call _pW->Foo().

Is there a way to achieve this?


Solution

  • You might use (or reimplement) std::experimental::propagate_const

    and then your code would be:

    class Widget {
    public:
        void Foo();
        void FooConst() const;
    };
    
    class WidgetManager {
    public:
        WidgetManager() : _pW(std::make_shared<Widget>()) {}
    
        void ManagerFoo()
        {
            _pW->Foo();      // OK
            _pW->FooConst(); // OK
        }
    
        void ManagerFooConst() const
        {
            _pW->Foo();         // not compile
            _pW->FooConst();    // OK
        }
    
    private:
        std::experimental::propagate_const<std::shared_ptr<Widget>> _pW;
    };
    

    Demo