В каком смысле const допускает только atomi c изменения изменяемых переменных-членов? - PullRequest
4 голосов
/ 08 мая 2020

Я читаю «Функциональное программирование на C ++» из Ивана Чукича , и мне трудно интерпретировать точку в резюме главы 5:

  • Когда вы создаете функцию-член const, вы обещаете, что функция не изменит никаких данных в классе (не изменится ни одна часть объекта) или что любые изменения объекта (для членов, объявленных как mutable) будет atomi c для пользователей объекта .

Если часть в itali c было просто ограничено членами, объявленными как mutable, я был бы доволен этим. Однако эта моя переформулировка, кажется, соответствует тому, что автор заключил в скобки. То, что выходит за рамки скобок, меня озадачивает: что означает atomi c в этом предложении?

Ответы [ 2 ]

6 голосов
/ 08 мая 2020

Автор заявляет о передовых методах, а не о правилах языка.

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

struct S {
    mutable int value = 0;
    int get() const {
        return value++;
    }
};
const S s;
std::cout << s.get();  // prints 0
std::cout << s.get();  // prints 1
// etc

Вы можете сделать это, и это не нарушит ни одно из правил языка. Однако вы не должны . Это нарушает ожидания пользователя о том, что метод const не должен изменять внутреннее состояние наблюдаемым образом.

Существуют законные применения для mutable членов, например мемоизация, которая может ускорить последующее выполнение const функция-член.

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

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

3 голосов
/ 08 мая 2020

или что любые изменения в объекте (для членов, объявленных как изменяемые) будут atomi c с точки зрения пользователей объекта.

Я думаю, что автор (или редактор) книги плохо сформулировал свое утверждение там - const и mutable не дают никаких гарантий относительно безопасности потоков; действительно, они были частью языка еще тогда, когда в языке не было поддержки многопоточности (то есть, когда спецификации многопоточности не были частью стандарта C ++, и поэтому все, что вы делали с многопоточностью в своей программе на C ++, было технически неопределенным поведением).

Я думаю, что автор намеревался передать, что изменения изменяемых переменных-членов из метода с тегами const должны быть ограничены только изменениями, которые не меняют состояние объекта до вызывающего кода могу сказать . Примером classi c этого может быть запоминание дорогостоящих вычислений для использования в будущем, например:

class ExpensiveResultGenerator
{
public:
    ExpensiveResultGenerator()
       : _cachedInputValue(-1)
    {
    }

    float CalculateResult(int inputValue) const
    {
       if ((_cachedInputValue < 0)||(_cachedInputValue != inputValue))
       {
          _cachedInputValue = inputValue;
          _cachedResult     = ReallyCPUExpensiveCalculation(inputValue);
       }
       return _cachedResult;
    }

private:
    float ReallyCPUExpensiveCalculation(int inputValue) const
    {
        // Code that is really expensive to calculate the value
        // corresponding to (inputValue) goes here....
        [...]
        return computedResult;
    }

    mutable int _cachedInputValue;
    mutable float _cachedResult;
}

Обратите внимание, что что касается кода, использующего класс ExpensiveResultGenerator, CalculateResult(int) const не меняет состояние объекта ExpensiveResultGenerator; он просто вычисляет математическую функцию и возвращает результат. Но внутри мы делаем оптимизацию запоминания, так что если пользователь вызывает CalculateResult(x) с одним и тем же значением для x несколько раз подряд, мы можем пропустить дорогостоящие вычисления после первого раза и просто вернуть _cachedResult вместо этого для ускорения.

Конечно, такая оптимизация памяти может привести к возникновению условий гонки в многопоточной среде, поскольку теперь мы меняем переменные состояния, даже если вызывающий код не видит, что мы Это. Итак, чтобы сделать это безопасно в многопоточной среде, вам нужно будет использовать какой-то Mutex для сериализации доступа к двум изменяемым переменным - либо это, либо требовать, чтобы вызывающий код сериализовал любые вызовы в CalculateResult().

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...