互斥体应该是可变的吗?

不知道这是一个风格的问题,或者有一个硬性的规则…

如果我想保持公共方法接口尽可能const,但使对象线程安全,我应该使用可变互斥锁? 一般来说,这是一种很好的风格,还是应该使用非const方法的接口呢? 请certificate你的看法。

[ 回复编辑 ]

基本上使用带有可变互斥的const方法是一个好主意(不要按照方式返回引用,确保按值返回),至less要表明它们不会修改对象。 互斥锁不应该是const的,将locking/解锁方法定义为const将是一个无耻的谎言…

其实这(和memoization)是我看到的mutable关键字的唯一公平的用途。

你也可以使用你的对象外部的互斥锁:安排所有的方法是可重入的,让用户自己pipe理锁: { lock locker(the_mutex); obj.foo(); } { lock locker(the_mutex); obj.foo(); } 不难打字,而且

 { lock locker(the_mutex); obj.foo(); obj.bar(42); ... } 

有优势,它不需要两个互斥锁(并保证对象的状态不会改变)。

隐藏的问题是:你在哪里把互斥锁保护你的class级?

作为总结,假设您想要读取受互斥锁保护的对象的内容。

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

如果互斥体是外部的

那么一切都好。 该对象可以是“常量”,互斥量不需要是:

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

恕我直言,这是一个不好的解决scheme,因为任何人都可以重用互斥体来保护别的东西。 包括你。 事实上,你会背叛自己,因为如果你的代码足够复杂,你只会对这个或那个互斥体究竟在保护什么而感到困惑。

我知道:我是这个问题的受害者。

如果互斥量是内部的

为了封装的目的,你应该把互斥体尽可能的靠近它所保护的对象。

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

一个好的方法是使用inheritance模板来派生原始对象,并添加互斥量特征:

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

这样,你可以写:

 int foo(const Mutexed<Object> & object) { Lock<Mutexed<Object> > lock(object) ; return object.read() ; } 

问题是,它不会工作,因为object是const的,锁对象正在调用非常量lockunlock方法。

困境

如果您认为const限于按位const对象,那么您就搞砸了,必须回到“外部互斥解决scheme”。

解决的办法是承认const更多的是一个语义限定符(当用作类的方法修饰符时,它是volatile的)。 你隐藏了这个类并不完全是const的事实,但是仍然要确保提供一个实现,保证在调用const方法时类的有意义的部分不会被改变。

然后你必须声明你的互斥体是可变的,并且locking/解锁方法是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 ; } 

内部互斥解决scheme是一个很好的解决scheme。恕我直言:一方面不得不一个接一个地声明一个对象,另一方面把它们集合在一个包装中,最后是一样的。

但是聚合具有以下优点:

  1. 这更自然(在访问对象之前locking对象)
  2. 一个对象,一个互斥体 由于代码风格迫使你遵循这种模式,它减less了死锁的风险,因为一个互斥体将只保护一个对象(而不是多个对象,你不会真的记得),一个对象将只受到一个互斥体的保护(而不是多个互斥量需要按正确的顺序locking)
  3. 上面的互斥类可以用于任何类

因此,请尽可能将互斥量保持在互斥对象(例如,使用上面的互斥体构造),然后为互斥量使用mutable限定符。

编辑2013-01-04

显然,Herb Sutter有相同的观点:他介绍C ++ 11中constmutable的“新”含义非常有启发性:

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