これがスタイルの質問なのか、それとも難しいルールがあるのかわかりません...
パブリックメソッドインターフェイスをできるだけconstにしたいが、オブジェクトをスレッドセーフにしたい場合は、可変ミューテックスを使用する必要がありますか?一般的に、これは良いスタイルですか、それとも非constメソッドインターフェースを優先すべきですか?あなたの見解を正当化してください。
[回答が編集されました]
基本的に、可変のmutexでconstメソッドを使用することをお勧めします(ちなみに参照を返さないでください。値で返すようにしてください)。少なくとも、オブジェクトを変更しないことを示します。 mutexはconstであってはなりません。ロック/ロック解除メソッドをconstとして定義するのは恥知らずな嘘です...
実際、これ(およびメモ化)は、mutable
キーワードの唯一の公正な使用法です。
また、オブジェクトの外部にあるミューテックスを使用することもできます。すべてのメソッドを再入可能にして、ユーザーにロックを自分で管理してもらいます。{ lock locker(the_mutex); obj.foo(); }
を入力するのはそれほど難しくありません。
{
lock locker(the_mutex);
obj.foo();
obj.bar(42);
...
}
2つのmutexロックを必要としないという利点があります(オブジェクトの状態が変更されなかったことが保証されます)。
隠された質問は、クラスを保護するミューテックスをどこに置くかです。
まとめとして、ミューテックスによって保護されているオブジェクトのコンテンツを読みたいとしましょう。
「読み取り」メソッドは、オブジェクト自体を変更しないため、意味的には「定数」である必要があります。ただし、値を読み取るには、ミューテックスをロックし、値を抽出してからミューテックスをロック解除する必要があります。つまり、ミューテックス自体を変更する必要があります。つまり、ミューテックス自体を「const」にすることはできません。
その後、すべて大丈夫です。オブジェクトは「const」にすることができ、mutexは次のようにする必要はありません。
Mutex mutex ;
int foo(const Object & object)
{
Lock<Mutex> lock(mutex) ;
return object.read() ;
}
私見、これは悪いソリューションです。誰かがmutexを再利用して他のものを保護できるからです。あなたを含みます。実際、コードが十分に複雑な場合、これまたはそのミューテックスが正確に保護しているものについて混乱するだけなので、あなたは自分を裏切ることになります。
私は知っています:私はその問題の犠牲者でした。
カプセル化の目的で、保護しているオブジェクトからできるだけ近くにミューテックスを置く必要があります。
通常は、内部にミューテックスを含むクラスを記述します。しかし、遅かれ早かれ、いくつかの複雑なSTL構造、または内部にミューテックスなしで別の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であり、ロックオブジェクトが非const lock
およびunlock
メソッドを呼び出しているため、機能しないことです。
const
がビット単位のconstオブジェクトに限定されていると思われる場合は、やっかいで、「外部mutexソリューション」に戻る必要があります。
解決策は、const
がよりセマンティックな修飾子であることを認めることです(クラスのメソッド修飾子として使用される場合はvolatile
と同様)。クラスが完全にconst
ではないという事実を隠していますが、const
メソッドを呼び出すときにクラスの意味のある部分が変更されないという約束を守る実装を必ず提供してください。
次に、ミューテックスをミュータブルに宣言し、lock/unlockメソッド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
修飾子を探します。
どうやら、ハーブサッターは同じ視点を持っています。C++ 11でのconst
とmutable
の「新しい」意味に関する彼のプレゼンテーションは非常に啓発的です。
http://herbsutter.com/2013/01/01/video-you-dont-know-const-and-mutable/