在什么意义上 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.
我认为这本书的作者(或编辑)在那里的陈述措辞不佳——const
和 mutable
不保证线程安全;事实上,当语言不支持多线程时,它们是语言的一部分(即当多线程规范不是 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.
的线程安全性的能力
我正在阅读 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 asmutable
) 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.
我认为这本书的作者(或编辑)在那里的陈述措辞不佳——const
和 mutable
不保证线程安全;事实上,当语言不支持多线程时,它们是语言的一部分(即当多线程规范不是 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.