在以下情况下,“指向 const bool 的指针”参数
pbAbort
有意义,因为辅助函数不会修改 bool 对象。但是,我担心编译器可能会优化对 bool 值的多次检查,除非我使用普通的“指向 bool 的指针”。 bool 是一个可以由管理器线程设置的标志。
void runWorkManager(DataSet& data)
{
bool bAbort = false;
callWorkerFuncFromNewThread(data, &bAbort);
while(!(data.isWorkCompleted || data.isWorkAborted))
{
updateGuiWithProgress(data.progress);
if(userWantsToAbort())
bAbort = true;
}
}
void workerFunc(DataSet& data, bool const *const pbAbort)
{
data.doPreWork();
if(*pbAbort) //Check #1
{
data.isWorkAborted = true;
return;
}
for(int i = 0; i < 100; ++i)
{
data.doWorkN(i);
if(*pbAbort) //Check #2
{
data.isWorkAborted = true;
return;
}
}
data.isWorkCompleted = true;
}
如果假设
*pbAbort
永远不会改变,那么编译器可以删除 Check #2 块。
c++ 11 标准 7.1.6.1.3 指出:
对 cv 限定类型的指针或引用实际上不需要指向或引用 cv 限定对象,但它会被视为指向或引用;即使引用的对象是非常量对象并且可以通过其他访问路径进行修改,const 限定的访问路径也不能用于修改对象。
不幸的是,这个说法并不能完全回答我的问题。
为了回答你的问题,如果编译器不相信变量正在被修改,它可以优化多次读取,无论对象是否是 const。考虑到读取线程中的代码路径不会写入变量,这种情况很可能发生在您的代码中。
这里需要注意的是,你的程序实际上包含未定义的行为;默认情况下,跨线程读取和写入变量不是原子的。为了安全地执行此操作,您需要一个
atomic<bool>
。另请参阅这个问题。另外,请勿使用 volatile
。它将解决您的重新排序问题,但对 volatile
变量的访问仍然不是原子的(因此仍然是 UB)。
标准声明没有回答您的问题的原因是它谈论的是一个线程内的读取和写入。
如果编译器无法证明指向的对象实际上是
const
,则标准中没有任何内容允许它假设它是 - 因此它无法优化读取。
(如果它可以看到更多代码 - 例如,如果函数是内联的或正在使用整个程序优化 - 那么当它知道没有写入时,它可能能够跳过读取。)
正如 this post 更详细地探讨的那样,拥有指向
const
的指针并不意味着编译器实际上可以在每种情况下假定指向的对象是 const
[在多线程/可中断环境中]。
正如 Herb Sutter 所说 [参见 此处]:
[只有]一种情况下“const”的真正含义是 某事,那就是当对象在它们所在的点被设为常量时 已定义。
其中真正的意思是指编译器[优化器]可以实现的可能优化。
他还写道[参见此处]:
const 主要是为人类服务的,而不是为编译器和优化器服务的。所以
const
对于程序员来说仍然是一件很棒的事情 - 只是不像通常认为的那样可用于编译器优化。
const
并不意味着对象不会改变,而是承诺不会通过这个名称修改对象。
对 cv 限定类型的指针或引用不需要实际指向 或引用一个 cv 限定的对象,但它被视为好像它确实如此;一个 const 限定的访问路径不能用于修改对象,即使 引用的对象是非常量对象,可以修改 通过其他一些访问路径。因此,在
bool const * const pAbort
中使用
workerFunc
只是表示,所指向的
bool
不得在此函数中修改。并不是说指向的
bool
永远不会改变。这就是编译器无法删除 Check #2 的原因。