Reputation: 773
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?
Upvotes: 3
Views: 507
Reputation: 217348
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;
};
Upvotes: 2
Reputation: 5675
In addition to what has been proposed here you can introduce a concept of "owning pointer" based on shared pointer:
template<class T>
class owning_ptr {
public:
owning_ptr(T* data) : mData{data} {}
// Put as many constructors as you need
T* operator->() { return mData.get(); }
const T* operator->() const { return mData.get(); }
private:
std::shared_ptr<T> mData;
};
This class has same functionality as shared pointer but a different concept of owning data.
You would use it the same way you'd use shared pointer:
class WidgetManager {
...
private:
owning_ptr<Widget> _pW;
}
Upvotes: 0
Reputation: 63775
Consider accessing your shared_ptr
through a member function that reflects the const-ness of this
onto the pointed-to object.
class WidgetManager {
...
private:
std::shared_ptr<Widget> _pW;
std::shared_ptr<Widget>& get_widget()
{
return _pW;
}
const std::shared_ptr<const Widget> get_widget() const
{
return _pW;
}
}
You will get the one error you expect here.
void ManagerFoo()
{
get_widget()->Foo(); // will be OK, will not compile if declared as "const Widget*"
get_widget()->FooConst(); // will be OK
}
void ManagerFooConst() const
{
get_widget()->Foo(); // will NOT be OK
get_widget()->FooConst(); // will be OK
}
Upvotes: 2
Reputation: 40080
An easy solution would be to make const and non-const managers two different types:
template<class TWidget>
class WidgetManager {
// ...
private:
std::shared_ptr<TWidget> _pW;
};
WidgetManager<const Widget> const_wm;
const_wm.ManagerFoo(); // fail: _pW->Foo(): call non-const function of *_pw: a const Widget.
Upvotes: 0