レガシーAPIとstd::unique_ptr

C++標準ライブラリで「スマートポインタ型」が提供されるようになり、現代のC++プログラムでは生ポインタ型(`T*`)＋`new`/`delete`による自前メモリ管理の手間と危険性から解放されました。

今どきC++アプリケーションのソースコードで手動メモリ管理をべた書きなんてしませんよね？


# レガシーAPIのリソース管理
...と言い切りたいところですが、現実問題としてはレガシーなC APIのみ提供される3rd Partyライブラリを使わざるを得ない場面はまだまだ存在します。

この手のライブラリAPIではリソース管理に生ポインタを用い、手動のメモリ確保＋解放相当関数が提供されるインタフェースが一般的です。[^1]

[^1]: C言語APIを提供するライブラリヘッダでは、C++プログラム向けに`extern "C"`リンケージを指定すべきです。本記事の主題からそれるため、コード例示では省略しています。

```c: legacy-api.h
// レガシーAPIを提供する3rd Partyライブラリのヘッダ
// 各関数は成功=値0／失敗=非0エラーコードを返す

// リソース型(宣言のみ)
struct Resource;

// メモリ確保＋リソース生成
// 第1引数経由で新規リソースへのポインタ値を返す
int legacylib_create(Resource**, int flag);

// リソース破棄＋メモリ解放
// 引数へのNULL指定は何もしない
int legacylib_destroy(Resource*);

// リソースを用いた処理
int legacylib_process(Resource*, int arg);
```


## ナイーブ実装

C++プログラム中で上記レガシーAPIを使う場合、メモリ解放処理が漏れる＝リソースリークが生じないよう注意深いコーディングが求められます。<del>正直やってられない。</del>

```c++: app-naive.cpp
// レガシーAPIを利用するC++アプリケーションコード
#include "legacy-api.h"

// 別途定義されたC++関数
int calculate_value(int);

// レガシーAPIを利用する関数
int process_resource(int flag, int arg)
{
  int err;
  Resource* resource = NULL;

  // リソース生成
  err = legacylib_create(&resource, flag);
  if (err != 0) { goto exit_func; }

  // リソースを用いた処理群
  err = legacylib_process(resource, arg);
  if (err != 0) { goto exit_func; }
  try {
    arg = calculate_value(arg);
    // C++関数は例外スローの可能性がある
  } catch (...) {
    // リソース破棄
    legacylib_destroy(resource);
    throw; // 例外再スロー
  }
  err = legacylib_process(resource, arg);
  if (err != 0) { goto exit_func; }

exit_func:
  // リソース破棄
  legacylib_destroy(resource);
  return err;
}
```


## std::unique_ptr＋デリータクラス(C++11以降)
C++標準ライブラリの[`std::unique_ptr`スマートポインタ型][unique_ptr]を用いると、レガシーAPIであってもリソース管理の手間を大幅に削減できます。

[unique_ptr]: https://cpprefjp.github.io/reference/memory/unique_ptr.html

```c++:app-cpp11.cpp
#include <memory>
#include "legacy-api.h"

// 別途定義されたC++関数
int calculate_value(int);

// Resource用デリータ型
struct resource_deleter {
  void operator()(Resource* res) {
    legacylib_destroy(res);
  }
};
// Resource管理スマートポインタ型
using ResourcePtr =
  std::unique_ptr<Resource, resource_deleter>;

int process_resource(int flag, int arg)
{
  int err;
  ResourcePtr resource; // 暗黙にnullptr初期化

  // リソース生成
  Resource* raw_resource;
  err = legacylib_create(&raw_resource, flag);
  if (err != 0) { return err; }
  // リソース管理をResourcePtr型に委譲
  resource.reset(raw_resource);

  // リソースを用いた処理群
  err = legacylib_process(resource.get(), arg);
  if (err != 0) { return err; }
  arg = calculate_value(arg);
  err = legacylib_process(resource.get(), arg);
  if (err != 0) { return err; }
  return 0;
}
```


## std::unique_ptr＋デリータラムダ式(C++20以降)
C++20で追加された言語機能[^2]を用いると、カスタムデリータ型の代わりにラムダ式（の型）を指定できます。

[^2]: [ステートレス・ラムダ式はデフォルト構築可能](https://cpprefjp.github.io/lang/cpp20/default_constructible_and_assignable_stateless_lambdas.html) と [decltype演算子へのラムダ式指定](https://cpprefjp.github.io/lang/cpp20/wording_for_lambdas_in_unevaluated_contexts.html) の複合技。

```cpp:app-cpp20.cpp(型定義のみ)
// Resource管理スマートポインタ型
using ResourcePtr = std::unique_ptr<Resource,
  decltype([](Resource* res){ legacylib_destroy(res); })>;
```

ラムダ式を用いるデリータ定義は少々暗号めいているため、素直なカスタムデリータクラス定義との選択は、個人の好みやプロジェクトの判断にお任せします。


## std::unique_ptr＋std::out_ptr(C++23以降)
C++20標準ライブラリ時点では、生ポインタ(`raw_resource`)によるリソース獲得とスマートポインタ型への委譲(`reset()`)周りのコードが冗長になるのは否めません。

```cpp:(リソース生成処理のみ)
  ResourcePtr resource;

  // リソース生成
  Resource* raw_resource;
  err = legacylib_create(&raw_resource, flag);
  if (err != 0) { return err; }
  // リソース管理をResourcePtr型に委譲
  resource.reset(raw_resource);
```

C++23標準ライブラリへ導入予定の[スマートポインタアダプタ`std::out_ptr`][out_ptr]を用いると、`std::unique_ptr`スマートポインタ型への管理委譲を簡潔に記述できます。

[out_ptr]: https://cpprefjp.github.io/reference/memory/out_ptr.html

```cpp:app-cpp23.cpp(関数のみ)
int process_resource(int flag, int arg)
{
  int err;
  ResourcePtr resource;

  // リソース生成
  err = legacylib_create(std::out_ptr(resource), flag);
  if (err != 0) { return err; }

  // リソースを用いた処理群
  err = legacylib_process(resource.get(), arg);
  if (err != 0) { return err; }
  arg = calculate_value(arg);
  err = legacylib_process(resource.get(), arg);
  if (err != 0) { return err; }
  return 0;
}
```

♪ はやく来い来いC++23