Как применить const-корректность в отношении указателей на члены-данные - PullRequest
0 голосов
/ 08 июня 2018

После обсуждения на работе мы, похоже, не можем навязать «логическую» 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 и неконстантных "getter of 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 ]

0 голосов
/ 08 июня 2018

Вы можете использовать (или переопределить) 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;
};

Демо

0 голосов
/ 08 июня 2018

Подумайте о том, чтобы получить доступ к вашему 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
}
0 голосов
/ 08 июня 2018

В дополнение к тому, что было предложено здесь, вы можете ввести концепцию «владения указателем», основанную на разделяемом указателе:

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;
}
0 голосов
/ 08 июня 2018

Простым решением было бы сделать 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.
...