40
20

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

C++Advent Calendar 2023

Day 2

レガシーAPIとstd::unique_ptr

Last updated at Posted at 2023-12-01

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

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

レガシーAPIのリソース管理

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

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

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を使う場合、メモリ解放処理が漏れる=リソースリークが生じないよう注意深いコーディングが求められます。正直やってられない。

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スマートポインタ型を用いると、レガシーAPIであってもリソース管理の手間を大幅に削減できます。

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を用いると、カスタムデリータ型の代わりにラムダ式(の型)を指定できます。

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())周りのコードが冗長になるのは否めません。

(リソース生成処理のみ)
  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を用いると、std::unique_ptrスマートポインタ型への管理委譲を簡潔に記述できます。

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

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

  2. ステートレス・ラムダ式はデフォルト構築可能decltype演算子へのラムダ式指定 の複合技。

40
20
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
40
20

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?