不确定这是一个样式问题,还是一个有硬规则的东西......
如果我想让公共方法接口尽可能保持const,但是使对象线程安全,我应该使用可变的互斥锁吗?一般来说,这是一个好样式,还是应该首选非const方法接口?请证明你的观点。
答案 0 :(得分:49)
隐藏的问题是:你在哪里使用互斥锁保护你的班级?
总结一下,假设您想要读取受互斥锁保护的对象的内容。
“read”方法应该在语义上是“const”,因为它不会更改对象本身。但是要读取该值,您需要锁定互斥锁,提取值,然后解锁互斥锁,这意味着必须修改互斥锁本身,这意味着互斥锁本身不能是“常量”。
然后一切都好。对象可以是“const”,互斥量不需要是:
Mutex mutex ;
int foo(const Object & object)
{
Lock<Mutex> lock(mutex) ;
return object.read() ;
}
恕我直言,这是一个糟糕的解决方案,因为任何人都可以重用互斥锁来保护别的东西。包括你。事实上,你会背叛自己,因为如果你的代码足够复杂,你只会对这个或那个互斥锁正在保护的东西感到困惑。
我知道:我是那个问题的受害者。
出于封装目的,您应该将互斥锁尽可能地放在它正在保护的对象上。
通常,你会在里面写一个带有互斥锁的类。但是迟早,你需要保护一些复杂的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 ;
}
这样,你可以写:
int foo(const Mutexed<Object> & object)
{
Lock<Mutexed<Object> > lock(object) ;
return object.read() ;
}
问题是它不起作用,因为object
是const,而lock对象正在调用非const lock
和unlock
方法。
如果你认为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 ;
}
内部互斥解决方案是一个很好的解决方案恕我直言:拥有一方面一个接近另一方声明的对象,另一方面让它们聚集在一个包装器中,最终是同样的事情。
但聚合有以下优点:
因此,请将互斥锁尽可能靠近互斥对象(例如,使用上面的互斥结构),然后转到互斥锁的mutable
限定符。
显然,Herb Sutter有相同的观点:他在C ++ 11中关于const
和mutable
的“新”含义的介绍非常具有启发性:
http://herbsutter.com/2013/01/01/video-you-dont-know-const-and-mutable/
答案 1 :(得分:28)
[回答编辑]
基本上使用带有可变互斥锁的const方法是一个好主意(顺便说一下,不要返回引用,确保按值返回),至少表明它们不修改对象。互斥量不应该是常量,将锁定/解锁方法定义为常量将是一种无耻的谎言......
实际上,这(和备忘录)是我在mutable
关键字中看到的唯一公平用途。
您还可以使用对象外部的互斥锁:安排所有方法都是可重入的,让用户自己管理锁:{ lock locker(the_mutex); obj.foo(); }
并不难打字,而且
{
lock locker(the_mutex);
obj.foo();
obj.bar(42);
...
}
的优点是它不需要两个互斥锁(并保证对象的状态不会改变)。