C++ コア ガイドライン:ついに C++ に

私の主張を明確にするために、この投稿は、例外をスローできないという例外的なケースについてです。プログラムが制限付きの組み込み環境で実行される場合、またはハード リアルタイム要件を満たす必要がある場合、この状況はそれほど例外的ではないかもしれません。

例外をスローできない例外的な環境から始めましょう。私の当初の計画では、少なくとも規則 E.19 から E.27 について書くことでした。しかし、ルール E.19 で行き詰まります。

E.19:final_action 適切なリソース ハンドルが利用できない場合にクリーンアップを表現するオブジェクト

final_action について聞いたことがないので、最初のルールに驚かれるかもしれません。私も。そこで調べました。調査中に、Bartłomiej Filipek によるこのルールに関する優れた投稿を見つけました。 . Bartłomiej Filipek は、有名な C++ ブログ、Bartek のコーディング ブログの著者です。彼の許可を得て、GSL からの彼の投稿 Beautiful code:final_act を私の投稿に喜んで組み込みます。ここにいます。

スコープの最後で特別なアクションを呼び出す必要がある場合があります。それは、コード、フラグ セット、コード ガード、関数呼び出しの開始/終了などを解放するリソースである可能性があります。その場合。
gsl::final_actに会いましょう /finally .

はじめに

フォローアップ ここに投稿:リンク。

次のコードがあるとします:

void addExtraNodes();
void removeExtraNodes();

bool Scanner::scanNodes()
{
 // code...
 addExtraNodes();

 // code...
 removeExtraNodes();
 return true;
}

scanNodes のオブジェクトがたくさんあります (グローバルまたは共有コンテナー) をスキャンしますが、チェックするノードをいくつか追加する必要があります。コンテナーの初期状態を維持したいので、最後に追加のノードを削除する必要があります。

もちろん、スキャン コード全体の設計は、コンテナーのコピーで作業し、余分なものを追加または削除することが問題にならないように、はるかに優れている可能性があります。しかし、特にレガシー コードでは、グローバル コンテナーで作業する場所があり、それを変更するときは特別な注意が必要です。状態を変更すると、多くのバグが発生する可能性があり、誰かが共有コンテナーの別の状態を期待しています。

私のコードは期待どおりに動作しているようです...そうですか? removeExtraNodes と呼びます

しかし、scanNodes から複数のリターンがある場合はどうでしょうか。 ?簡単です:removeExtraNodes に複数の呼び出しを追加する必要があります . わかりました….

いくつかの例外がスローされた場合はどうなりますか?次に、スローする前にクリーンアップ関数を呼び出す必要もあります…

removeExtraNodes を呼び出す必要があるようです 最後の帰還前だけじゃない!

助けが必要

C++ コア ガイドラインを見てみましょう。彼らは次のことを提案しています:

E.19:適切なリソース ハンドルが利用できない場合は、final_action オブジェクトを使用してクリーンアップを表現します

ガイドラインには、より良い設計を目指して努力する必要があると書かれていますが、それでも後藤よりはましです。アプローチを終了するか、何もしないでください。

わかりました…しかし、ここでの解決策は何ですか:

bool Scanner::scanNodes()
{
 // code...
 addExtraNodes();
 auto _ = finally([] { removeExtraNodes(); });

 // code...

 return true;
}

ここで何が起こったのですか?

removeExtraNodes への呼び出しをラップするだけでした デストラクタで特定の呼び出し可能なオブジェクトを呼び出す特別なオブジェクト。これこそまさに私たちが必要としているものです!

その魔法の finally() はどこにありますか コード?

ガイドライン サポート ライブラリ/gsl_util.h を参照してください。

ボンネットの下

コードは短いので、ここに貼り付けることもできます:

template <class F>
class final_act
{
public:
 explicit final_act(F f) noexcept 
 : f_(std::move(f)), invoke_(true) {}

 final_act(final_act&& other) noexcept 
 : f_(std::move(other.f_)), 
 invoke_(other.invoke_)
 {
 other.invoke_ = false;
 }

 final_act(const final_act&) = delete;
 final_act& operator=(const final_act&) = delete;

 ~final_act() noexcept
 {
 if (invoke_) f_();
 }

private:
 F f_;
 bool invoke_;
};

素敵ですね!

上記のクラスは呼び出し可能なオブジェクト - f_ を取ります -そして、それが破壊されようとしているときにそれを呼び出します。そのため、コードが早期に返されたり、例外がスローされたりした場合でも、クリーンアップ コードを呼び出す必要があります。

移動セマンティクスとうまく連携するには、追加のブール値パラメーター invoke_ が必要です。 .これにより、一時オブジェクトのコードを呼び出さないことが保証されます。必要に応じて詳細については、このコミットを参照してください:
Final_act のコピー/移動のセマンティクスが間違っています。

C++17 では、クラス テンプレートの Template 引数推定があります。そのため、final_act オブジェクトを次のように宣言することもできます:

final_act _f([] { removeExtraNodes(); })

C++17 より前では、ヘルパー関数を最終的に使用して作業を楽にする必要があります:

template <class F>
inline final_act<F> finally(const F& f) noexcept
{
 return final_act<F>(f);
}

template <class F>
inline final_act<F> finally(F&& f) noexcept
{
 return final_act<F>(std::forward<F>(f));
}

全体として、 finally() を使用できます クライアント コードの関数。 C++17 では、クラス テンプレートの Template 引数推定が得られるため、変更される可能性があります。

このコードのいいところは何ですか?

  • クリーンでシンプルなコード
  • 表現力豊か、コメント不要
  • 1 つのことだけを行う
  • ジェネリックなので、呼び出し可能なものなら何でも動作します
  • 最新の C++:移動セマンティクス、noexcept、をサポートする

重要な注意:最終行為は noexcept にする必要があります

GSL リポジトリ (たとえば、こちら) のコメントで何度も説明されているように、他の問題や Final_act から、最終的な行為が例外をスローした場合、プログラムの終了につながる可能性があります:

Final_act は noexcept である必要があります。概念的には、ユーザーがデストラクタを呼び出す便利な方法であり、デストラクタは noexcept にする必要があります。呼び出したものがスローされた場合、プログラムは終了します。

つまり、他のデストラクタ コードと同じ前提で呼び出されるコードを作成する必要があるため、そこには何もスローしないでください。単なるクリーンアップではなく、「通常の」コードを呼び出したい場合、これは少し制限になる可能性があります (一方で、それは結局のところ悪い設計になるのでしょうか?)。

どこで使用できますか?

明確にするために:finally は使用しないでください 近づきすぎ!適切に設計されていれば、オブジェクトはグローバルな状態で機能せず、RAII を最大限に活用する必要があります。それでも、finally の状況があります。 使いやすい:

  • 取引。これは、何かが失敗したときに元に戻す必要があるすべてのアクションの一般的な用語です。ファイルの 95% をコピーしてエラーが発生した場合、破損している可能性のあるファイルをそのままにしておくことはできません。それらを削除して、もう一度やり直す必要があります。データベースに接続していて、いくつかのレコードを書きたい場合は、それがアトミックであると想定します .
  • begin/end 関数 - end を呼び出す必要がある場所 何かが始まった後。私たちの例のように。
  • フラグセッター。共有フラグがあり、新しい状態に設定しましたが、完了したら古い状態にリセットする必要があります。
  • RAII をサポートしないリソース。ガイドラインは、malloc/free の例を示しています。 RAII オブジェクトでラップできない場合 (たとえば、スマート ポインターやカスタム デリーターを使用)、final_act
  • 接続を安全に閉じる - 実際のリソース クリーンアップの別の例として。

final_act の場所は他にもありますか?

次のリストも参照してください:Reddit にしばらく登場した ScopeGuard の C++ リスト (スレッドはこちら)

まとめ

フォローアップ ここに投稿:リンク。

final_act /finally 物を掃除するという汚い仕事を助けることができる、美しくよく設計されたツールです。あなたのコードでは、物事/リソースをクリーンにするためのより良いアプローチを採用する必要がありますが、それが不可能な場合は final_act 素晴らしいソリューションです。

同様のクラスを使用してコード内のものをきれいにしていますか?

次は?

例外をスローできず、最終的に使用できない場合は、問題があります。この問題は次の投稿で解決します。

詳細情報

最近 Bartłomiej Filipek 彼の最初の本 C++17 in Detail を出版しました .新しい標準を効果的かつ実用的な方法で学びたい場合は、こちらの本をご覧ください:https://leanpub.com/cpp17indetail.

無料:C++ の 4 つのバウチャーの詳細

Bartłomiej Filipek 彼の本の引換券を 4 枚くれました。詳細については、こちらをご覧ください:無料:4 枚のバウチャーを獲得。