Правильная альтернатива "изменяемой функции" в С++

У меня часто возникают проблемы с правильностью констант при переносе алгоритмов в классах в c++. Я чувствую, что хочу изменяемую функцию, хотя это запрещено. Может кто-нибудь посоветовать мне, как реализовать классы, такие как следующий?

Ниже приведен код, который я хочу написать.

  • Функция run() не должна быть константная функция, потому что она меняет данные.
  • Функция get_result() должна быть постоянной функцией (насколько это касается пользователя), потому что она возвращает данные.

Однако, если пользователь запрашивает результат без вызова run(), я хочу, чтобы функция get_result() запускала алгоритм. Это нарушает правильность const, потому что у меня есть const-функция, вызывающая неконстантную функцию.

class operate_on_data
{
  std::vector<double> m_data;  // the data to modify
  bool m_completed;  // check to see if the function run() has been called
public:
  operate_on_data(std::vector<double> data)
    : m_data(data), m_completed(false) {}  //initialise
  void run() //I don't want this function to be const  
  {
    //The algorithm goes here - it alters m_data.
    m_completed = true;  //the algorithm has been run
  }
  std::vector<double> get_result() const //I want this function to be const
  {
    /*The following breaks const correctness because 
      I am calling a non-const function from a const function
      - but this feels like the right thing to do ... */ 
    if (!m_completed) run();  //run the algorithm if it has not run already
    return m_data; //return
  }
};

Единственный способ, которым я смог скомпилировать вышеупомянутый класс, это либо

  • сделайте run() const и сделайте m_data и m_completed изменяемыми. Это работает, но концептуально неверно, потому что run() явно изменяет данные.
  • сделать get_result() не постоянной функцией. Это также кажется неправильным, поскольку пользователь может ожидать, что эта функция будет простым возвращением и, следовательно, постоянной.
  • Поместите функцию run() в const-функцию get_result() и сделайте переменные данных изменяемыми.

Насколько я понимаю, ключевое слово mutable было разработано для третьей из этих опций, где реализация требует изменения данных, но пользователь разумно ожидает простой возврат и, следовательно, функцию const.

Однако я не хочу делать этот последний вариант, потому что я хочу, чтобы пользователь мог точно выбирать, когда они изменяют данные. Однако есть вероятность, что они забудут вызвать run(), и поэтому я хочу принудительно запустить алгоритм, если они запросят результат без вызова run(). Я чувствую, что хочу сделать run() изменчивым, но мне нельзя.

Как правильно написать такой класс?

Ответ 1

make run() const и make m_data и m_completed mutable. Это работает, но концептуально неправильно, потому что run() явно меняет данные.

На самом деле это не так. Переменные в вашем классе, по сути, изменены, но вы никогда не сможете это продемонстрировать. Вызов run() не изменяет ничего, что пользователь может извлечь из вашего интерфейса класса. Если вы не можете получить какую-либо информацию о таком изменении, вы не сможете продемонстрировать это изменение. Это более чем семантическая проблема, она говорит непосредственно со всей точкой ключевого слова "изменчивое".

Ключевое слово "изменчивое" сильно непонятно.

Тем не менее, хотя с минимально предоставленной информацией я могу сделать это выше, я не рекомендую это. Там почти наверняка лучший метод, который был бы очевидным, учитывая более широкий обзор вашей проблемы.

Другим методом, который я могу использовать, является то, что вы, очевидно, пытаетесь избежать: заставить пользователя вызывать run() перед использованием get_data(). По правде говоря, это тоже субоптимальный метод. Возможно, более того.

Edit:

Если вы решите использовать изменяемый метод, я бы предложил некоторые изменения. Наличие функции "run()", которая является константой и не возвращает ничего интересного, будет довольно запутанной. Эта функция, безусловно, должна быть неконстантной. Таким образом, то, что я сделал бы, получив решение сделать это таким образом, уже имеет run() вызов функции const и private, которая имеет поведение текущей функции run(), на которую также ссылается get_data ( ) в указанных условиях.

Ответ 2

Некоторые абстрактные замечания, которые могут помочь вам прояснить ситуацию:

  • const методы - это те, которые не изменяют концептуальное "состояние" объектов,
  • Не const метод - это те, которые делают.
  • Кроме того, поля mutable - это те, которые являются объектами, но не считаются частью концептуального объекта state (например, некоторые кешированные значения, которые оцениваются лениво и запоминаются).

Проблема может заключаться в том, что operate_on_data не может быть действительно определенным классом. Что такое объект класса "oper_on_data"? Что такое "состояние" этого объекта? Что не является? Это звучит неловко (по крайней мере для меня) - и неловкое звучание описания какой-то конструкции может указывать на контр-интуитивный дизайн.

Моя мысль заключается в том, что вы сохраняете разные понятия "операция" и "результат операции" в одном странном классе, что приводит к путанице.

Ответ 3

Просто сделайте ошибку (часть интерфейса), чтобы получить результат до запуска алгоритма. Затем вы отделяете работу от результатов, позволяя им правильно указывать их константу. Метод get может бросить, если вы попытаетесь вызвать его до запуска алгоритма, чтобы указать клиенту, что они делают что-то неправильно.

Ответ 4

Я думаю, что ваша проблема является семантической, а не синтаксической.

Запрос результата без вызова run() сначала - это ошибка, на мой взгляд, и должна привести к исключению.

Если это не ошибка и действительно возможно, я не вижу смысла иметь run() в первую очередь, поэтому просто отбросьте его и выполните всю работу в (не const) get_result().

Ответ 5

Если get_result() может фактически изменить данные, это не будет const. Если вы хотите, чтобы он был const, не вызывайте run(), а скорее генерируйте исключение.

Вы должны использовать mutable для кэшированных данных, т.е. вещи, которые не изменяют состояние вашего экземпляра и сохраняются только по соображениям производительности.

Ответ 6

Если я когда-либо вступлю в эту должность, я бы, вероятно, выбросил исключение.

Однако вы можете уйти с

if (!m_completed) (const_cast <operate_on_data *> (this))->run();

Однако, если get_result вызывается в экземпляре operate_on_data, который фактически определен как const, вы вводите lala-land.

Ответ 7

Если единственное, что run() изменяется в объекте, это m_completed, тогда это нормально объявить m_completed mutable и run() const. Если run() изменяет другие вещи, то вызов get_result() также изменит эти другие вещи, а значение get_result() не должно быть const.

Однако, чтобы завершить обсуждение, вы заметите, что каждый контейнер STL имеет две функции begin() и две функции end(). Одна функция begin() и одна end() возвращают изменяемые итераторы, в то время как другие функции begin() и end() возвращают const_iterator s.

Фактически можно перегрузить run() версией const и non const. Затем get_result() будет вызывать const версию run(), потому что он будет считаться единственным законным вариантом:

class operate_on_data
{
    std::vector<double> m_data;
    bool m_completed;
public:
    operate_on_data(std::vector<double> data)
        : m_data(data), m_completed(false) { }
    void run()
    {
       //The algorithm goes here - it alters m_data.
       m_completed = true;
    }

    void run() const
    {
        // something that does not modify m_data or m_completed
    }
    std::vector<double> get_result() const
    {
        if (!m_completed)
            run();
        return m_data;
    }
};

Однако это имеет смысл только в том случае, если версия const run() не изменяет никакого состояния. В противном случае не const -ность run() просочится в get_result() и сделает const -ness get_result() вопиющей ложью.


Я предполагаю, что примерный код несколько надуман. Если нет, вы в основном принимаете это:

std::vector<double> results = do_calculation(data);

И оберните его в объект с очень тонким интерфейсом (а именно, метод get_results(), который возвращает std::vector<double>). Я не вижу большого улучшения в объектно-ориентированной версии. Если вы хотите кэшировать результаты вашего расчета, обычно это имеет смысл сделать, просто поддерживая std::vector<double> вокруг int кодом, который бы создал и использовал этот объект.

Ответ 8

Концепция const зависит от реализации класса; что важна логическая семантика, а не константа уровня уровня/уровня. Поэтому, если нужно вызвать неконстантный метод из const, просто const cast. Я предпочитаю:

void X::foo(  ) const {
      X & self = const_cast<X &>(*this);
      self.bar( ); //"bar" non-const function
}