74

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

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

4

2 に答える 2

66

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

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

「read」メソッドは、オブジェクト自体を変更しないため、意味的に「const」である必要があります。ただし、値を読み取るには、ミューテックスをロックし、値を抽出してから、ミューテックスのロックを解除する必要があります。つまり、ミューテックス自体を変更する必要があります。つまり、ミューテックス自体を「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オブジェクトがnon-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 ;
}

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

ただし、集計には次の長所があります。

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

したがって、mutexをmutexedオブジェクトにできるだけ近づけて(たとえば、上記のMutexed構造を使用して)、mutablemutexの修飾子を探します。

2013-01-04を編集

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

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

于 2010-11-08T22:22:25.603 に答える
38

[回答編集]

基本的に、可変ミューテックスでconstメソッドを使用することは、少なくともオブジェクトを変更しないことを示すために、良い考えです(ちなみに、参照を返さないでください。必ず値で返すようにしてください)。ミューテックスはconstであってはなりません。lock/unlockメソッドをconstとして定義するのは恥知らずな嘘です...

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

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

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

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

于 2010-11-08T19:38:06.603 に答える