【2024年版】try catch finally を C++ で実現する

C++ には Java や C# のような try catch finally がありません(VC++の独自拡張は除く)。
いたってマジメに作りました。
今回使用しているヘッダ(on_scope_exit.h および try_finally.h)は文末に載せておきます。
品質の確保ができましたので、on_scope_exit.h および try_finally.h を v1.0.0 として公開します。
どんどん使ってください。
これらの機能を使うために、Boost とか入れなくてもいいです。

<b>2023/04/16 12:40 の版を v1.0.0 としました。</b>

## 解法1 クラスインスタンスがスコープ外になるときに呼ばれるデストラクタを使う

```cpp:test01.cpp
#include "on_scope_exit.h"
#include <stdio.h>
#include <iostream>

int main()
{
    std::cout << "start" << std::endl;
    FILE *f = fopen("file.txt", "w");
    ON_SCOPE_EXIT({
        std::cout << "before fclose()" << std::endl;
        fclose(f);
    });
    fprintf(f, "the answer is %d\n", 11 + 22);
    std::cout << "after fprintf()" << std::endl;
    return 0;
}
```

```text:test01.cppの処理結果
start
after fprintf()
before fclose()
```

上記のコードの以下の部分は・・・

```
    ON_SCOPE_EXIT({
        std::cout << "before fclose()" << std::endl;
        fclose(f);
    });
```

以下のように展開されます：

```
    scope_guard 変数名([&]() {
        std::cout << "before fclose()" << std::endl;
        fclose(f);
    });
```

scope_guard型のインスタンスをラムダで初期化して、そのインスタンスがスコープ外に出るさいに呼ばれるscope_guardクラスのデストラクタから、ラムダ(この場合、fclose処理)を呼び出します。

try finally なんて書いてないで、スコープガードを使って処理を呼ばせればいいじゃないか・・・というわけです。
私もなかなか使える答えだなぁと思いましたが、一つ(しかも重要な)落とし穴がありました。

### 例外が起きた時の挙動に問題あり

```cpp:test01.cpp(改造版)
#include "on_scope_exit.h"
#include <stdio.h>
#include <iostream>

int main()
{
    std::cout << "start" << std::endl;
    FILE *f = fopen("file.txt", "w");
    ON_SCOPE_EXIT({
        std::cout << "before fclose()" << std::endl;
        fclose(f);
    });
    fprintf(f, "the answer is %d\n", 11 + 22);
    std::cout << "after fprintf()" << std::endl;
    throw std::runtime_error("error-1"); // ← 追加しました
    return 0;
}
```

```text:test01.cpp(改造版)の実行結果
start
after fprintf()
terminate called after throwing an instance of 'std::runtime_error'
  what():  error-1
exit status 3
```

お気づきでしょうか、ON_SCOPE_EXIT() の処理が呼ばれてません。
「before fclose()」というメッセージが表示されてません。
try (catch) finally の finally は例外があっても実行されるところがありがたい(場合もある)のです・・・

## 解法2 try catch を魔改造して、疑似 try catch finally を作り出す

これは、面白いソースがいろいろありました。
私なりに整理してヘッダを作ってみました。

```test02.cpp
#include "try_finally.h"
#include <stdio.h>
#include <iostream>

int main()
{
    FILE *f = nullptr;
    TRY
    {
        std::cout << "start" << std::endl;
        f = fopen("file.txt", "w");
        fprintf(f, "the answer is %d\n", 11 + 22);
        std::cout << "after fprintf()" << std::endl;
        throw std::runtime_error("error-1");  // ← 追加しました
    }
    FINALLY
    {
        std::cout << "before fclose()" << std::endl;
        if (f) fclose(f);
    }
    END;
    return 0;
}
```

```test02.cppの処理結果
start
after fprintf()
before fclose()
terminate called after throwing an instance of 'std::runtime_error'
  what():  error-1
exit status 3
```

TRY部でstd::runtime_errorがスローされているにも関わらず、「before fclose()」というメッセージが出力されています。

せっかく try なんですから catch もしちゃいましょう。
catch句でも例外が throw されるかもしれません。それでもうまく動作するでしょうか？

```cpp:test03.cpp
#include "try_finally.h"
#include <stdio.h>
#include <iostream>

int main()
{
    FILE *f = nullptr;
    TRY
    {
        std::cout << "start" << std::endl;
        f = fopen("file.txt", "w");
        fprintf(f, "the answer is %d\n", 11 + 22);
        std::cout << "after fprintf()" << std::endl;
        throw std::runtime_error("error-1");  // ← 追加しました
    }
    CATCH(std::runtime_error &err)
    {
        throw std::runtime_error("error-2");  // ← 追加しました
    }
    FINALLY
    {
        std::cout << "before fclose()" << std::endl;
        if (f) fclose(f);
    }
    END;
    return 0;
}
```

catch句の中で throw した error-2 が返りました。

```test03.cppの処理結果
start
after fprintf()
before fclose()
terminate called after throwing an instance of 'std::runtime_error'
  what():  error-2
exit status 3
```

## まとめ

* TRY (CATCH) FINALLY END; は強力ですが、例外が絡まない場合には書きぶりもふくめて使い易いとはいえない
* Win32 API は例外を投げないので、Win32 API の資源を回収するような用途では scope guard を使った ON_SCOPE_EXIT の方が使い出があるかもしれません。

## 最後に

以下に本記事で使った2つのヘッダを添付します。
ご不明な点や質問・指摘事項などありましたらコメントをお願いします。

それでは！

```cpp:on_scope_exit.h
/* on_scope_exit.h v1.0.0          */
/* Last Modified: 2023/04/16 12:40 */

#include <functional>

namespace global
{
class scope_guard
{
    std::function<void()> f;

public:
    explicit scope_guard(std::function<void()> f) : f(f) {}
    scope_guard(scope_guard const &) = delete;
    void operator=(scope_guard const &) = delete;
    ~scope_guard() { f(); }
};
}  // namespace global

#define ON_SCOPE_EXIT_COMBINE1(X, Y) X##Y
#define ON_SCOPE_EXIT_COMBINE(X, Y) ON_SCOPE_EXIT_COMBINE1(X, Y)
#define ON_SCOPE_EXIT(code)                                   \
    global::scope_guard ON_SCOPE_EXIT_COMBINE(on_scope_exit_, \
                                              __LINE__)([&]() code)
```

```cpp:try_finally.h
/* try_finally.h v1.0.0            */
/* Last Modified: 2023/04/16 12:40 */

#include <exception>
#include <functional>

namespace global
{
inline void try_finally(const std::function<void()> &try_code,
                        const std::function<void()> &finally_code)
{
    try
    {
        try_code();
    }
    catch (...)
    {
        std::exception_ptr ep = std::current_exception();
        try
        {
            finally_code();
        }
        catch (...)
        {
            ep = std::current_exception();
        }
        if (ep)
        {
            std::rethrow_exception(ep);
        }
        std::terminate();  // never reach here
    }
    try
    {
        finally_code();
    }
    catch (...)
    {
        std::exception_ptr ep = std::current_exception();
        if (ep)
        {
            std::rethrow_exception(ep);
        }
        std::terminate();  // never reach here
    }
}
class never_thrown_exception
{
};
}  // namespace global

#define TRY   global::try_finally([&](){ try
#define CATCH catch
#define FINALLY                                    \
    catch (const global::never_thrown_exception &) \
    {                                              \
    }                                              \
    }                                              \
    , [&]()
#define END   )
```