質問
マルチスレッド アプリケーションのクラッシュをデバッグしているときに、最終的に次のステートメントで問題を特定しました。
CSingleLock(&m_criticalSection, TRUE);
CSingleLock クラスの名前のないオブジェクトを作成しているため、このステートメントの直後にクリティカル セクション オブジェクトのロックが解除されることに注意してください。これは明らかにプログラマが望んでいたものではありません。このエラーは単純な入力ミスが原因で発生しました。私の質問は、コンパイル時自体にクラスの一時オブジェクトが作成されるのを何らかの方法で防ぐことができるかということです。上記のタイプのコードはコンパイラ エラーを生成するはずです。一般に、クラスが何らかのリソースの取得を試みるときは常に、そのクラスの一時オブジェクトは許可されるべきではないと思います。それを強制する方法はありますか?
解決
編集: j_random_hacker が指摘しているように、ロックを解除するためにユーザーに名前付きオブジェクトの宣言を強制することが可能です。
ただし、クラスで一時ファイルの作成が何らかの理由で禁止されていたとしても、ユーザーは同様の間違いを犯す可能性があります。
// take out a lock:
if (m_multiThreaded)
{
CSingleLock c(&m_criticalSection, TRUE);
}
// do other stuff, assuming lock is held
最終的には、ユーザーは自分が作成したコード行の影響を理解する必要があります。この場合、オブジェクトを作成していることと、それがどれくらい持続するかを知る必要があります。
もう一つ考えられる間違い:
CSingleLock *c = new CSingleLock(&m_criticalSection, TRUE);
// do other stuff, don't call delete on c...
「クラスのユーザーがヒープにクラスを割り当てるのを阻止する方法はあるだろうか?」という質問につながるでしょう。答えは同じです。
C++0x では、ラムダを使用してこれらすべてを行う別の方法が用意されています。関数を定義します。
template <class TLock, class TLockedOperation>
void WithLock(TLock *lock, const TLockedOperation &op)
{
CSingleLock c(lock, TRUE);
op();
}
この関数は CSingleLock の正しい使用法をキャプチャします。次に、ユーザーにこれを実行させます。
WithLock(&m_criticalSection,
[&] {
// do stuff, lock is held in this context.
});
これはユーザーにとって失敗するのがはるかに困難です。構文は最初は奇妙に見えますが、コード ブロックに続く [&] は、「引数を取らない関数を定義し、名前で何かを参照し、それが外部の何かの名前である場合 (例:含まれる関数内のローカル変数) 非 const 参照でアクセスできるようにするため、変更できます。)
他のヒント
初め、 ハサミムシはいくつかの良い点を指摘しています -- この構造の偶発的な誤用をすべて防ぐことはできません。
ただし、特定のケースでは、これは実際には回避できます。それは、C++ が一時オブジェクトに関して 1 つの (奇妙な) 区別を行っているためです。 Free 関数は、一時オブジェクトへの非 const 参照を取得できません。 したがって、ロックが突然存在したり消滅したりするのを避けるには、ロック コードを外部に移動するだけです。 CSingleLock
コンストラクターを無料の関数に追加します (これを友達にして、内部をメソッドとして公開することを避けることができます)。
class CSingleLock {
friend void Lock(CSingleLock& lock) {
// Perform the actual locking here.
}
};
ロック解除は引き続きデストラクター内で実行されます。
使用するには:
CSingleLock myLock(&m_criticalSection, TRUE);
Lock(myLock);
はい、書くのが少し面倒です。しかし今、次のことを試みるとコンパイラはエラーを出します。
Lock(CSingleLock(&m_criticalSection, TRUE)); // Error! Caught at compile time.
なぜなら、非const refパラメータは Lock()
一時的なものにバインドできません。
おそらく驚くべきことに、クラスメソッド できる 一時的なもので運用する -- だからこそ Lock()
無料の機能である必要があります。落としてしまったら、 friend
指定子と関数パラメータを上部のスニペットに含めます。 Lock()
メソッドを使用すると、コンパイラは次のように記述できるようになります。
CSingleLock(&m_criticalSection, TRUE).Lock(); // Yikes!
MS コンパイラの注意: Visual Studio .NET 2003 までの MSVC++ バージョンでは、VC++ 2005 より前のバージョンの非 const 参照への関数のバインドが誤って許可されていました。 この動作は VC++ 2005 以降で修正されています。.
いいえ、これを行う方法はありません。無名の一時を作成することに大きく依存しているほぼすべてのC ++のコードを壊すそう。特定のクラスのためのあなたの唯一の解決策は、彼らのコンストラクタをプライベートにして、常に工場のいくつかの並べ替えを経由して、それらを構築することです。しかし、私は治療法は病気よりも悪いと思います!
私はそうは思いません。
それを行うための賢明な事はありませんが - あなたはバグで出発見したとして - 文の「違法」何もありません。コンパイラは、メソッドからの戻り値が「不可欠」であるか否かを知る方法はありません。
コンパイラは、私見を一時オブジェクトの作成を禁止するべきではありません。
ベクトルを縮小するなどの特別な場合は、あなたが本当に作成する一時的なオブジェクトが必要です。
std::vector<T>(v).swap(v);
は、それが少し難しいですが、まだコードレビューとユニットテストは、これらの問題をキャッチする必要があります。
それ以外の場合は、ここでは1人の貧乏人のソリューションがあります:
CSingleLock aLock(&m_criticalSection); //Don't use the second parameter whose default is FALSE
aLock.Lock(); //an explicit lock should take care of your problem
次はどう?少し乱用プリプロセッサが、それは、私はそれが含まれるべきだと思うことを十分に賢いです。
class CSingleLock
{
...
};
#define CSingleLock class CSingleLock
次ながらので、さて、エラーが一時的に名前を付け忘れがあり、有効なC ++ます:
class CSingleLock lock(&m_criticalSection, true); // Compiles just fine!
同じコードが、名前を省略しない
class CSingleLock(&m_criticalSection, true); // <-- ERROR!
私は5年間で誰が最も簡単な解決策を考え出すていないことを参照してください。
#define LOCK(x) CSingleLock lock(&x, TRUE);
...
void f() {
LOCK(m_criticalSection);
そして今だけのロックを作成するために、このマクロを使用しています。任意のより多くの一時変数を作成する機会はありません!これは、マクロを容易デバッグチェックインの任意の種類を実行するように拡張することができる付加的な利点は、不適切な、再帰的ロック、ロックの記録ファイルと行、およびはるかに検出する、例えば、ビルドしている。