Как обеспечить const-корректность в отношении данных-членов указателя
После обсуждения в работе, мы, кажется, не в состоянии обеспечить "логическую" const-корректность для класса, который имеет данные-члены указателя, как таковые:
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;
};
Как можно видеть, мы хотели бы иметь WidgetManager::ManagerFooConst()
, Чтобы не иметь возможности вызывать неконстантные функции членов указателя WidgetManager
, в то же время позволяя им вызываться из других, неконстантных функций WidgetManager
. Это означает, что объявление указателя как std::shared_ptr<const Widget>
(то есть const Widget*
) не выполняется.
Кроме того, мы хотели бы иметь возможность сделать указатель ссылается на другой Widget
в течение жизни менеджера, поэтому мы действительно не хотим удерживать его как элемент данных (и не можем удерживать его по ссылке).
Конечно, вся" побитовая " const-корректность применяется здесь, так как никакие данные-члены WidgetManager
не могут быть изменены из методов const (включая конкретные Widget
, на которые указывает _pW
), но мы хотели бы достичь "логической" const-корректности, если бы даже указываемые члены не могли быть изменены.
Единственное, что мы придумали, это добавление const и non-const "геттеров this
" к Widget
:
class Widget {
public:
void Foo();
void FooConst() const;
Widget* GetPtr() { return this; }
const Widget* GetConstPtr() const { return this; }
};
И возвращаясь к использованию их вместо оператора стрелки напрямую:
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();
}
Но это так уродливо, и определенно не может быть осуществлено компилятором.
В частности, попытка перегрузить operator->
для Widget*
и const Widget*
, похоже, ничего не изменила: ManagerFooConst()
все еще мог вызывать _pW->Foo()
.
4 ответа:
Вы можете использовать (или переопределить)
std::experimental::propagate_const
И тогда ваш код будет:
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; };
Рассмотрите возможность доступа к вашему
shared_ptr
через функцию-член, которая отражает постоянствоthis
на объект с указателем.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; } }
Вы получите одну ошибку, которую ожидаете здесь.
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 }
Простым решением было бы сделать const и non-const менеджеров двух различных типов:
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.
В дополнение к тому, что было предложено здесь, вы можете ввести понятие "владения указателем", основанное на разделяемом указателе:
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; };
Этот класс имеет ту же функциональность, что и общий указатель, но другую концепцию владения данными.
Вы бы использовали его так же, как вы бы использовали общий указатель:
class WidgetManager { ... private: owning_ptr<Widget> _pW; }