ホームに戻る
出典 :
関連 :
目次 :
ガード節とは
関数(サブルーチン)において、処理を継続することが不可能(または無意味)となった場合に、関数の途中で脱出する記法。
フローが単純化されて可読性が向上するとともに、処理速度の向上も期待できる。
ガード節を使用しない場合
bool Func()
{
bool ret = true; //< 戻り値
if ( DoSomething01() )
{
if ( DoSomething02() )
{
if ( DoSomething03() )
{
// すべて成功
ret = true;
}
else
{
// DoSomething03() 失敗
ret = false;
}
}
else
{
// DoSomething02() 失敗
ret = false;
}
}
else
{
// DoSomething01() 失敗
ret = false;
}
return ret;
}
この例では、正常系の処理が進むにつれて if 節は長く、ネストは深くなるため、正常系の処理が追跡しづらい。
また、異常系の処理は if 節の後に記述されることから、if 文による条件判定と異常系の処理との間に行数が空き、同じくフローが煩雑となる。
(関数全体を眺めないとフローが把握できない。)
ガード節を使用した場合
bool Func()
{
// DoSomething01() 失敗時は抜ける
if ( !DoSomething01() )
{
return false;
}
// DoSomething02() 失敗時は抜ける
if ( !DoSomething02() )
{
return false;
}
// DoSomething03() 失敗時は抜ける
if ( !DoSomething03() )
{
return false;
}
// すべて正常
return true;
}
この例では、異常があった場合には関数を抜けるようになっており、コードが簡潔で意図が伝わりやすい。また、戻り値を格納する一時変数も不要である。
ガード節を用いる上での原則は、「異常があった場合に途中で脱出すること」。
ガード節を用いる上での注意
bool Func()
{
// セマフォ獲得
_wait_sem();
// DoSomething01() 失敗時は抜ける
if ( !DoSomething01() )
{
// ここで抜けるとセマフォを握ったままとなる
return false;
}
// DoSomething02() 失敗時は抜ける
if ( !DoSomething02() )
{
// ここで抜けるとセマフォを握ったままとなる
return false;
}
// セマフォ解放
_release_sem();
// すべて正常
return true;
}
上記のように関数内でセマフォやミューテックス、ヒープを使用している場合、単純にガード節を用いるのみでは資源の解放漏れが発生し得る。
このため、以下のような手段で解決を図る必要がある。
本体をサブルーチン化
// Func_Core()のシンタクスシュガー
bool Func()
{
bool ret;
// セマフォ獲得
_wait_sem();
// (セマフォの中で行う)本体の処理
ret = Func_Core();
// セマフォ解放
_release_sem();
return ret;
}
// 本体の処理 (Func()のサブルーチン)
bool Func_Core()
{
// DoSomething01() 失敗時は抜ける
if ( !DoSomething01() )
{
return false;
}
// DoSomething02() 失敗時は抜ける
if ( !DoSomething02() )
{
return false;
}
// すべて正常
return true;
}
Func() で行いたい処理をサブルーチンである Func_Core() に移し、Func() を Func_Core() の糖衣とする手法。
セマフォの獲得・解放はサブルーチン外で行っているため、Func_Core() がどのタイミングで終了してもセマフォの解放漏れは発生しない。
しかし、上記のコードでは例外が発生した場合にセマフォの解放漏れが発生し得る。例外に対応するためには以下のように記述する。
(ただし後述のRAIIを用いる方が簡便かつ確実である。)
// Func_Core()のシンタクスシュガー
bool Func()
{
bool ret = false;
// セマフォ獲得
_wait_sem();
// Func_Core() で発生した例外を捕捉
try
{
ret = Func_Core();
}
catch(...)
{
// セマフォ解放
_release_sem();
// (Func() 外で例外を捕捉している場合)例外を投げなおす
throw;
}
// セマフォ解放
_release_sem();
return ret;
}
// 本体の処理 (Func()のサブルーチン)
bool Func_Core()
{
// DoSomething01() 失敗時は抜ける
if ( !DoSomething01() )
{
return false;
}
// DoSomething02() 失敗時は抜ける
if ( !DoSomething02() )
{
return false;
}
// すべて正常
return true;
}
(C++)RAIIを用いる
RAIIについてはリンク先を参照。
// セマフォの獲得・解放専用クラス
Class C_SemaphoreManager
{
public:
// コンストラクタ : セマフォ獲得
C_SemaphoreManager() { _wait_sem(); }
private:
// デストラクタ : セマフォ解放
~C_SemaphoreManager() { _release_sem(); }
};
// Func()
bool Func()
{
// セマフォ管理用インスタンス(地蔵)生成(セマフォ獲得)
C_SemaphoreManager semStature;
// DoSomething01() 失敗時は抜ける
if ( !DoSomething01() )
{
// ここで抜けてもデストラクタが走る
// ⇒ セマフォ解放
return false;
}
// DoSomething02() 失敗時は抜ける
if ( !DoSomething02() )
{
// ここで抜けてもデストラクタが走る
// ⇒ セマフォ解放
return false;
}
// すべて正常
// ここで抜けてもデストラクタが走る
// ⇒ セマフォ解放
return true;
}
C_SemaphoreManager はコンストラクタでセマフォを獲得、デストラクタで解放を行うクラスである。
semStature の生成時にセマフォを獲得。Func() をどの時点で抜けたとしてもスコープを抜けた際にデストラクタが走るため、確実にセマフォを解放できる。
(例外が発生した場合でも同様にデストラクタが走る。)
リソースの解放のためだけに try - catch 節を記述する必要が無いため、コードが簡潔となる。