Работа с ленивыми вычислениями в классах C++

Скажем, у меня есть класс:

class NumberCollection
{
public:
    typedef std::set<int> SetType;
    typedef SetType::iterator iterator;
    void insert(int n);

    iterator begin();
    iterator end();
    size_t size() const;

    iterator difficultBegin();
    iterator difficultEnd();
    size_t difficultSize() const;    

private:
    SetType easySet_, difficultSet_;
}

Где insert() добавляет элемент к easySet_. Участники difficultSet_ меняются в зависимости от участников easySet_.

Проблема, с которой я сталкиваюсь, заключается в том, что множественные вставки означают, что difficultSet_ постоянно пересчитывается. Поэтому я хочу, чтобы difficultSet_ вычислялся лениво (т. е. только при вызове difficultBegin(), difficultEnd() или difficultSize()). Проблема в том, что тогда мне действительно нужно превратить difficultSet_ в mutable, иначе difficultSize() не сможет с ним работать.

Итак, теперь мое объявление класса выглядит так

class NumberCollection
{
public:
    typedef std::set<int> SetType;
    typedef SetType::iterator iterator;
    void insert(int n);

    iterator begin();
    iterator end();
    size_t size() const;

    iterator difficultBegin();
    iterator difficultEnd();
    size_t difficultSize() const;    

private:
    SetType easySet_; 
    mutable SetType difficultSet_;
    mutable bool upToDate_;
}

Я чувствую, что это плохой дизайн, хотя. Есть ли способ лучше?


person rlbond    schedule 19.05.2009    source источник
comment
Почему вы объявляете метод hardSize() с ключевым словом const? Есть ли для этого особая причина?   -  person Wacek    schedule 19.05.2009
comment
@Wacek: Потому что метод, который принимает параметр NumberCollection const&, должен иметь возможность вызывать сложноSize() (я полагаю). Но вы правы, вы можете понизить этот метод до неконстантного, чтобы сделать тот факт, что он может обновлять кешированное представление, более явным.   -  person j_random_hacker    schedule 19.05.2009
comment
Бывают случаи, когда мне действительно нужно вызвать hardSize() для константной ссылки, и по логике вещей проверка размера не меняет объект.   -  person rlbond    schedule 19.05.2009
comment
если вы работаете с константными объектами, вы можете добавить константные версии функций итераторов, которые возвращают const_iterators.   -  person wilhelmtell    schedule 19.05.2009


Ответы (4)


Это полностью способ сделать это. Константа может означать бинарную константу или концептуально константу. Использование mutable означает, что вы делаете позже, и это нормально.

person Don Neufeld    schedule 19.05.2009
comment
+1. Я считаю, что mutable был добавлен в язык именно для этого случая — кэширования логически константных значений. - person j_random_hacker; 19.05.2009
comment
Вы правы в C++98. В C++11 вы можете синхронизировать доступ к изменяемым данным. Подробнее см. мой ответ. - person Philipp Claßen; 04.01.2013

Чтобы понять, зачем использовать mutable, мы можем изучить другие варианты.

Вы можете решить ту же проблему, используя const_cast:

size_t NumberCollection::difficultSize() const
{
     if(!upToDate_)
     {
          NumberCollection& nonConst = const_cast<NumberCollection&>(*this);
          nonConst.difficultSet_ = PerformExpensiveCalculationFunction();
          nonConst.upToDate_ = true;
     }
     // etc....
}

Предложив это решение, скажу, что оно уступает использованию mutable. Если элемент помечен как изменяемый, то, просто взглянув на заголовок, я могу понять, как вы к нему относитесь. Я не получу эту информацию, если вы используете const_cast.

Но тогда кто-то может принять другую сторону дискуссии и сказать, что лучше не раскрывать подробности реализации в шапке.

person Andrew Shepherd    schedule 19.05.2009

По сути, это причина, по которой C++ имеет изменяемую конструкцию. разглагольствование Алана Де Смета о неправильном использовании mutable показывает виды ситуаций, в которых mutable не следует использовать.

В этом случае, функция hardSize() не меняет того, что представляет собой NumberCollection, что подходит для пометки как const. Однако время от времени ему нужно изменять внутренние компоненты, поэтому вам нужно пометить сложные наборы_ и upToDate_ как изменяемые.

person garethm    schedule 19.05.2009

Ваше решение отлично подходит для С++ 98. Обратите внимание, что в С++ 11 вам следует подумать о синхронизации доступа к вашим изменяемым данным. В противном случае вы можете столкнуться с проблемами, когда ваш класс используется STL, который предполагает, что все константные функции-члены являются потокобезопасными.

Дополнительные сведения см. в разделе Означает ли const потокобезопасность в C+ +11?

person Philipp Claßen    schedule 04.01.2013