在什么意义上 const 只允许对可变成员变量进行原子更改?

In what sense const allows only atomic changes to mutable member variables?

我正在阅读 Ivan Čukić 的 C++ 函数式编程,我很难解释第 5 章摘要中的一个要点:

  • When you make a member function const, you promise that the function won't change any data in the class (not a bit of the object will change), or that any changes to the object (to members declared as mutable) will be atomic as far as the users of the object are concerned.

如果斜体中的部分只是仅限于声明为mutable的成员,我会很高兴的.但是,我的改写似乎与作者在括号中的内容相对应。括号里的是让我百思不得其解的地方:那句话里的atomic是什么意思?

or that any changes to the object (to members declared as mutable) will be atomic as far as the users of the object are concerned.

我认为这本书的作者(或编辑)在那里的陈述措辞不佳——constmutable 不保证线程安全;事实上,当语言不支持多线程时,它们是语言的一部分(即当多线程规范不是 C++ 标准的一部分时,因此你在 C++ 程序中对多线程所做的任何事情在技术上都是未定义的行为)。

我认为作者想要传达的是,使用 const 标记的方法对可变成员变量的更改应仅限于不更改对象状态的更改 调用代码可以告诉。 classic 的例子是记忆昂贵的计算以供将来参考,例如:

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 class 的代码而言,CalculateResult(int) const 不会更改 ExpensiveResultGenerator 对象的状态;它只是简单地计算一个数学函数并 returning 结果。但在内部我们正在进行记忆化优化,这样如果用户连续多次使用相同的值 x 调用 CalculateResult(x),我们可以在第一次后跳过昂贵的计算,只需 return 改为 _cachedResult,以加快速度。

当然,进行记忆化优化可能会在多线程环境中引入竞争条件,因为现在我们正在更改状态变量,即使调用代码看不到我们这样做。因此,要在多线程环境中安全地执行此操作,您需要使用某种 Mutex 来序列化对两个可变变量的访问——或者要求调用代码序列化对 CalculateResult().[ 的任何调用。 =21=]

作者声明的是最佳实践,而不是语言规则。

可以写一个class,其中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 成员函数后续执行的记忆。

作者建议,作为最佳实践,const 成员函数 mutable 成员的这种使用应该 是原子的,因为用户很可能期望两个不同的线程可以同时调用一个对象的const成员函数。

如果您违反了这条准则,那么您并没有直接违反任何语言规则。但是,这使得用户可能会以导致数据竞争(未定义行为)的方式使用您的 class。它剥夺了用户使用 const 限定符来推断 class.

的线程安全性的能力