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

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

提问人:Enlico 提问时间:5/9/2020 更新时间:5/9/2020 访问量:855

问:

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

  • 当您创建成员函数时,您承诺该函数不会更改类中的任何数据(对象不会更改任何数据),或者就对象的用户而言,对对象的任何更改(对声明为可变的成员)都是原子的。const

如果斜体部分只是仅限于声明为可变的成员,我会很高兴。然而,我的这种改写似乎与作者在括号中的内容相对应。括号里的东西让我感到困惑:那句话中原子的意思是什么?

C++(英语:C++) 语言律师 常数 原子 可变

评论

2赞 Evg 5/9/2020
观看Herb Sutter的演讲:C++ and Beyond 2012: Herb Sutter - You don't know const and mutable.
0赞 Enlico 5/9/2020
@Evg,谢谢你的建议。也许看完之后我就能自己回答了,如果之前没有其他人给出好的答案。
0赞 Evg 5/9/2020
@EnricoMariaDeAngelis,在那段视频中,赫伯·萨特(Herb Sutter)认为,在C++11中,它们与螺纹安全有着内在的联系。这是一个微妙的问题,所以我只是发布了该链接,而不是试图在答案中总结视频。constmutable

答:

3赞 Jeremy Friesner 5/9/2020 #1

或者对对象的任何更改(对声明为可变的成员) 就对象的用户而言,将是原子的。

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

我认为作者想要传达的是,使用 const-tagged 方法对可变成员变量的更改应仅限于调用代码无法更改对象状态的更改。这方面的典型例子是将昂贵的计算记录下来以备将来参考,例如:

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;
}

请注意,就使用类的代码而言,不会更改对象的状态;它只是计算一个数学函数并返回结果。但是在内部,我们正在进行记忆化优化,以便如果用户连续多次使用相同的值进行调用,我们可以在第一次之后跳过昂贵的计算,而只返回 为了加速。ExpensiveResultGeneratorCalculateResult(int) constExpensiveResultGeneratorCalculateResult(x)x_cachedResult

当然,进行记忆化优化可能会在多线程环境中引入竞争条件,因为现在我们正在更改状态变量,即使调用代码看不到我们这样做。因此,为了在多线程环境中安全地执行此操作,您需要使用某种互斥锁来序列化对两个可变变量的访问 -- 要么是那个,要么是需要调用代码来序列化对 .CalculateResult()

评论

0赞 Evg 5/9/2020
我想作者的重点不是这样,他们自己也做出保证,而是代码应该以某种方式编写,以便在设计上是线程安全的。这是你的承诺。constmutableconstmutable
7赞 Brian Bi 5/9/2020 #2

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

您可以编写一个类,其中的方法以用户可见的方式更改成员,如下所示:constmutable

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

成员有合法的用途,例如记忆,可以加快成员函数的后续执行。mutableconst

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

如果你违反了这个准则,那么你并没有直接违反任何语言规则。但是,它使用户可能会以导致数据争用(这是未定义的行为)的方式使用您的类。它剥夺了用户使用限定符来推理类的线程安全性的能力。const