C++標準ライブラリで「スマートポインタ型」が提供されるようになり、現代のC++プログラムでは生ポインタ型(T*
)+new
/delete
による自前メモリ管理の手間と危険性から解放されました。
今どきC++アプリケーションのソースコードで手動メモリ管理をべた書きなんてしませんよね?
レガシーAPIのリソース管理
...と言い切りたいところですが、現実問題としてはレガシーなC APIのみ提供される3rd Partyライブラリを使わざるを得ない場面はまだまだ存在します。
この手のライブラリAPIではリソース管理に生ポインタを用い、手動のメモリ確保+解放相当関数が提供されるインタフェースが一般的です。1
// レガシー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を使う場合、メモリ解放処理が漏れる=リソースリークが生じないよう注意深いコーディングが求められます。正直やってられない。
// レガシー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であってもリソース管理の手間を大幅に削減できます。
#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を用いると、カスタムデリータ型の代わりにラムダ式(の型)を指定できます。
// 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
スマートポインタ型への管理委譲を簡潔に記述できます。
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
-
C言語APIを提供するライブラリヘッダでは、C++プログラム向けに
extern "C"
リンケージを指定すべきです。本記事の主題からそれるため、コード例示では省略しています。 ↩