web-dev-qa-db-ja.com

mutexは変更可能にする必要がありますか?

これがスタイルの質問なのか、それとも難しいルールがあるのか​​わかりません...

パブリックメソッドインターフェイスをできるだけconstにしたいが、オブジェクトをスレッドセーフにしたい場合は、可変ミューテックスを使用する必要がありますか?一般的に、これは良いスタイルですか、それとも非constメソッドインターフェースを優先すべきですか?あなたの見解を正当化してください。

58
Marcin

[回答が編集されました]

基本的に、可変のmutexでconstメソッドを使用することをお勧めします(ちなみに参照を返さないでください。値で返すようにしてください)。少なくとも、オブジェクトを変更しないことを示します。 mutexはconstであってはなりません。ロック/ロック解除メソッドをconstとして定義するのは恥知らずな嘘です...

実際、これ(およびメモ化)は、mutableキーワードの唯一の公正な使用法です。

また、オブジェクトの外部にあるミューテックスを使用することもできます。すべてのメソッドを再入可能にして、ユーザーにロックを自分で管理してもらいます。{ lock locker(the_mutex); obj.foo(); }を入力するのはそれほど難しくありません。

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

2つのmutexロックを必要としないという利点があります(オブジェクトの状態が変更されなかったことが保証されます)。

28
Alexandre C.

隠された質問は、クラスを保護するミューテックスをどこに置くかです。

まとめとして、ミューテックスによって保護されているオブジェクトのコンテンツを読みたいとしましょう。

「読み取り」メソッドは、オブジェクト自体を変更しないため、意味的には「定数」である必要があります。ただし、値を読み取るには、ミューテックスをロックし、値を抽出してからミューテックスをロック解除する必要があります。つまり、ミューテックス自体を変更する必要があります。つまり、ミューテックス自体を「const」にすることはできません。

Mutexが外部の場合

その後、すべて大丈夫です。オブジェクトは「const」にすることができ、mutexは次のようにする必要はありません。

Mutex mutex ;

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

私見、これは悪いソリューションです。誰かがmutexを再利用して他のものを保護できるからです。あなたを含みます。実際、コードが十分に複雑な場合、これまたはそのミューテックスが正確に保護しているものについて混乱するだけなので、あなたは自分を裏切ることになります。

私は知っています:私はその問題の犠牲者でした。

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

内部ミューテックスソリューションは良いものです。私は、一方のオブジェクトを他方のオブジェクトの近くで宣言し、もう一方のオブジェクトをラッパーで集約することは、結局同じことです。

しかし、集約には次の長所があります。

  1. それはより自然です(アクセスする前にオブジェクトをロックします)
  2. 1つのオブジェクト、1つのミューテックス。コードスタイルはこのパターンに従うことを強制するので、1つのミューテックスは1つのオブジェクトのみを保護し(実際には覚えていない複数のオブジェクトは保護しない)、1つのオブジェクトは1つのミューテックスのみ(および正しい順序でロックする必要がある複数のミューテックス)
  3. 上記のミューテックスクラスはどのクラスにも使用できます

したがって、ミューテックスをミューテックスオブジェクトにできるだけ近づけて(たとえば、上記のミューテックスコンストラクトを使用して)、ミューテックスのmutable修飾子を探します。

2013-01-04を編集

どうやら、ハーブサッターは同じ視点を持っています。C++ 11でのconstmutableの「新しい」意味に関する彼のプレゼンテーションは非常に啓発的です。

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

51
paercebal