互斥量应该是可变的吗?

Mar*_*cin 57 c++ mutex mutable

不确定这是一个风格问题,还是一个有硬规则的东西......

如果我想尽可能保持公共方法接口为const,但是使对象线程安全,我应该使用可变互斥锁吗?一般来说,这是一个好样式,还是应该首选非const方法接口?请证明你的观点.

pae*_*bal 54

隐藏的问题是:你在哪里使用互斥保护你的班级?

总结一下,假设您要读取受互斥锁保护的对象的内容.

"read"方法应该在语义上是"const",因为它不会改变对象本身.但是要读取该值,您需要锁定互斥锁,提取值,然后解锁互斥锁,这意味着必须修改互斥锁本身,这意味着互斥锁本身不能是"常量".

如果互斥锁是外部的

一切都好.对象可以是"const",互斥量不需要是:

Mutex mutex ;

int foo(const Object & object)
{
   Lock<Mutex> lock(mutex) ;
   return object.read() ;
}
Run Code Online (Sandbox Code Playgroud)

恕我直言,这是一个糟糕的解决方案,因为任何人都可以重用互斥锁来保护其他东西.包括你.事实上,你会背叛自己,因为如果你的代码足够复杂,你只会对这个或那个互斥锁正在保护的东西感到困惑.

我知道:我是那个问题的受害者.

如果互斥锁是内部的

出于封装目的,您应该将互斥锁尽可能地放在它所保护的对象附近.

通常,你会写一个内部有互斥的类.但迟早,你需要保护一些复杂的STL结构,或者其他没有互斥体的东西(这是一件好事).

执行此操作的一种好方法是使用添加互斥锁功能的继承模板派生原始对象:

template <typename T>
class Mutexed : public T
{
   public :
      Mutexed() : T() {}
      // etc.

      void lock()   { this->m_mutex.lock() ; }
      void unlock() { this->m_mutex.unlock() ; } ;

   private :
      Mutex m_mutex ;
}
Run Code Online (Sandbox Code Playgroud)

这样,你可以写:

int foo(const Mutexed<Object> & object)
{
   Lock<Mutexed<Object> > lock(object) ;
   return object.read() ;
}
Run Code Online (Sandbox Code Playgroud)

问题是它不起作用因为object是const,而lock对象正在调用非const lockunlock方法.

困境

如果你认为const只限于按位const对象,那么你就搞砸了,必须回到"外部互斥解决方案".

解决方案是承认const更多是语义限定符(volatile当用作类的方法限定符时).您隐藏了类不完整的事实,const但仍然确保提供一个实现,以保证在调用const方法时不会更改类的有意义部分.

然后,您必须声明您的互斥锁是可变的,以及锁定/解锁方法const:

template <typename T>
class Mutexed : public T
{
   public :
      Mutexed() : T() {}
      // etc.

      void lock()   const { this->m_mutex.lock() ; }
      void unlock() const { this->m_mutex.unlock() ; } ;

   private :
      mutable Mutex m_mutex ;
}
Run Code Online (Sandbox Code Playgroud)

内部互斥解决方案是一个很好的解决方案恕我直言:拥有一方面彼此声明的对象,另一方面让它们在包装器中聚合,最终是相同的.

但聚合具有以下优点:

  1. 它更自然(你在访问它之前锁定对象)
  2. 一个对象,一个互斥体.由于代码样式强制您遵循此模式,因此可以降低死锁风险,因为一个互斥锁只能保护一个对象(而不是多个您不会记住的对象),并且一个对象仅受一个互斥锁保护(而不是需要按正确顺序锁定的多个互斥锁)
  3. 上面的互斥类可以用于任何类

因此,请将互斥锁尽可能靠近互斥对象(例如,使用上面的互斥结构),然后选择mutable互斥锁的限定符.

编辑2013-01-04

显然,香草萨特有相同的观点:他对"新"的含义演示constmutable在C++ 11是很有启发:

http://herbsutter.com/2013/01/01/video-you-dont-know-const-and-mutable/

  • @sbi:谢谢!事实是,我遇到了同样的问题,所以我觉得我必须添加一些上下文.OP隐藏的信息是他正在使用隐藏在他想要保护的类中的互斥锁,但是没有说清楚(我误解了第一个答案,并且认为Alexandre C.只建议使用外部互斥锁,当他写的时候在五行中我花了一本书来解释,所以我也应该责备... ... :-) ... (2认同)
  • 感谢Herb Sutter视频!值得的时间. (2认同)

Ale*_* C. 29

[ 答案编辑 ]

基本上使用带有可变互斥锁的const方法是一个好主意(顺便说一下,不要返回引用,确保按值返回),至少表明它们不修改对象.互斥体不应该是常量,将锁定/解锁方法定义为常量将是一种无耻的谎言......

实际上这个(和memoization)是我看到的mutable关键字的唯一合理用途.

您还可以使用对象外部的互斥锁:安排所有方法都是可重入的,并让用户自己管理锁:输入{ lock locker(the_mutex); obj.foo(); }并不难,并且

{
    lock locker(the_mutex);
    obj.foo();
    obj.bar(42);
    ...
}
Run Code Online (Sandbox Code Playgroud)

它的优点是它不需要两个互斥锁(并且保证对象的状态不会改变).

  • @VoidStar:这就是我所说的“记忆”。 (4认同)