ホームに戻る
出典 :
初心者向け。覚えておきたい 「ガード節」という書き方。 - Qiita if-then-elseを使うかガード節を使うかの判断基準 - MogLog
関連 :
ソフトウェア開発におけるアンチパターン 構造化プログラミング [C++]RAIIと地蔵インスタンス [C++]例外処理
目次 :

ガード節とは

関数(サブルーチン)において、処理を継続することが不可能(または無意味)となった場合に、関数の途中で脱出する記法。
フローが単純化されて可読性が向上するとともに、処理速度の向上も期待できる。

ガード節を使用しない場合

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 節を記述する必要が無いため、コードが簡潔となる。