RAII: инициализация элемента данных в методе const

В RAII ресурсы не инициализируются до тех пор, пока к ним не будет осуществлен доступ. Однако многие методы доступа объявлены постоянными. Мне нужно вызвать функцию mutable (неконстантную) для инициализации элемента данных.

Пример: загрузка из базы данных

struct MyClass
{
  int get_value(void) const;

  private:
     void  load_from_database(void); // Loads the data member from database.

     int m_value;
};

int
MyClass ::
get_value(void) const
{
  static bool value_initialized(false);
  if (!value_initialized)
  {
    // The compiler complains about this call because
    // the method is non-const and called from a const
    // method.
    load_from_database();
  }
  return m_value;
}

Мое примитивное решение - объявить элемент данных как mutable. Я бы предпочел не делать этого, потому что это предполагает, что другие методы могут изменить член.

Как бы я использовал оператор load_from_database(), чтобы избавиться от ошибок компилятора?


person Thomas Matthews    schedule 19.03.2010    source источник
comment
обязательно проинформируйте пользователя, что первый вызов ваших функций getXXX может занять некоторое время ... Или пользователи могут вызвать эту функцию в первый раз в критическом разделе, где важна производительность ...   -  person smerlin    schedule 19.03.2010
comment
@Thomas Matthews, вот ссылка, объясняющая, что такое RAII на самом деле en.wikipedia.org/wiki/Resource_Acquisition_Is_aInitialization >   -  person Glen    schedule 19.03.2010
comment
Вы можете / должны предоставить дополнительные функции инициализации, которые вызывают каждую функцию getXXX один раз ... чтобы пользователи могли вызывать эту функцию, если им не нужна отложенная инициализация   -  person smerlin    schedule 19.03.2010
comment
В RAII ресурсы не инициализируются до тех пор, пока к ним не будет осуществлен доступ. RAII означает Resource Acquisition Is Initialization, поэтому ваше первое утверждение неверно.   -  person John Dibling    schedule 19.03.2010
comment
Это static bool value_initialized(false); не только напоминает «самый неприятный синтаксический анализ», но и используется всеми экземплярами MyClass. Это желательно?   -  person mlvljr    schedule 19.03.2010
comment
Как сказал @John, в RAII; ресурсы приобретаются при инициализации объекта. Вот что означает имя .   -  person jalf    schedule 19.03.2010
comment
@ Даниэль: Это вторая половина, да. RAII действительно охватывает как конструктор, так и деструктор. Идея заключается в том, что время жизни ресурса должно быть сопоставлено с объектом: если объект существует, то существует и ресурс. Когда объект создается, ресурс приобретается, а когда объект уничтожается, ресурс освобождается. И строительство, и разрушение жизненно важны для правильного RAII.   -  person jalf    schedule 19.03.2010
comment
@mlvljr: переменная является локальной для функции (метода) и инициализируется до main() из-за квалификатора static. Таким образом, это хороший флаг для инициализации. Все экземпляры будут использовать его косвенно, поскольку он указывает, было ли инициализировано значение.   -  person Thomas Matthews    schedule 19.03.2010


Ответы (6)


Это не RAII. В RAII вы должны инициализировать его в конструкторе, который решит ваши проблемы.

Итак, вы используете Lazy. Будь то ленивая инициализация или ленивые вычисления.

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

Конечно, вы можете использовать const_cast, но что, если кто-то это сделает:

static const MyClass Examplar;

И компилятор решает, что это хороший кандидат на постоянную память? Что ж, в этом случае эффекты const_cast не определены. В лучшем случае ничего не происходит.

Если вы все еще хотите следовать по пути const_cast, сделайте это так же, как R Samuel Klatchko.

Если вы подумали и думаете, что, вероятно, есть лучшая альтернатива, вы можете решить обернуть свою переменную. Если бы он был в своем собственном классе, имея всего 3 метода: get, set и load_from_database, вы бы не беспокоились о том, что это mutable.

person Matthieu M.    schedule 19.03.2010
comment
... И за то, что он против const_cast, что почти всегда неверно - person laginimaineb; 19.03.2010

Вы в основном реализуете механизм кеширования. Лично я считаю, что можно пометить кешированные данные как изменяемые.

person StackedCrooked    schedule 19.03.2010
comment
Более того, похоже, что это просто обоснование самого существования mutable (members). - person mlvljr; 19.03.2010

Как уже отмечал Матье, то, что вы здесь пытаетесь сделать, имеет мало общего (если вообще есть) с RAII. Точно так же я сомневаюсь, что любая комбинация const и mutable действительно поможет. const и mutable изменяют тип и одинаково применяются ко всему доступу к объекту этого типа.

Кажется, вам нужно, чтобы небольшой объем кода имел доступ на запись, а все остальное - только на чтение значения. Учитывая базовый дизайн C ++ (и большинства подобных языков), правильный способ сделать это - переместить переменную в отдельный класс с небольшим количеством кода, которому требуется доступ для записи как часть (или, возможно, друг друга). ) этого класса. Остальному миру предоставляется доступ только для чтения через интерфейс класса (то есть функция-член, которая извлекает значение).

Опубликованный вами (предположительно урезанный) MyClass довольно близок к правому - вам просто нужно использовать его отдельно, а не как часть более крупного класса с множеством других членов. Главное, что нужно изменить: 1) имя с MyClass на что-то вроде lazy_int и 2) (по крайней мере, по моим предпочтениям) get_value(), вероятно, следует переименовать в operator int(). Да, m_value, вероятно, должен быть изменяемым, но это не позволяет другому коду записывать значение просто потому, что другой код вообще не имеет доступа к самому значению.

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

person Jerry Coffin    schedule 19.03.2010

[ПОСМОТРЕТЬ МА! БЕЗ КАСТОВ! :))]

struct DBValue 
{
  int get_value();

private:
  void load_from_database();
  int value;
};

struct MyClass 
{
  MyClass(): db_value(new DBValue()) {}
  ~MyClass() { delete db_value; } 

  int get_value() const;

private:
  DBValue * const db_value;
};

int MyClass::get_value() const
{
  return db_value->get_value(); // calls void load_from_database() if needed
}

Идея состоит в том, чтобы иметь политически корректный MyClass с const методами, не изменяющими ничего, кроме вызова как const, так и не const методов агрегированных объектов через константные указатели. .

person mlvljr    schedule 19.03.2010
comment
По сути, это то, что говорил Джерри Коффин, но вы предоставили пример кода. - person Thomas Matthews; 19.03.2010
comment
@Thomas Matthews На самом деле меня (частично) вдохновила последняя часть сообщения Матье М.. - person mlvljr; 20.03.2010

Не используйте здесь const_cast, иначе у вас будут проблемы. Использование изменяемого в этом случае не должно быть проблемой, но если профилировщик не предложил иное, я думаю, что пользователи будут менее удивлены, увидев объект, который дорого построить, чем метод доступа, который дорого вызывать в первый раз. .

person wilhelmtell    schedule 19.03.2010

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

Этот метод не потребует ни const_cast, ни mutable и сделает потенциально дорогостоящую операцию явной.

person Björn Pollex    schedule 19.03.2010
comment
Тема проблемы заключается в том, что переменная инициализируется с использованием lazy initialization, что является первым обращением к ней. Все последующие обращения к этой переменной доступны только для чтения. База данных не меняется, только порядок инициализации. - person Thomas Matthews; 19.03.2010