【C++学習|実務向け】C++17から標準化された std::shared_ptr による配列管理の勘所

導入: なぜ shared_ptr で配列を扱うのか

C++におけるメモリ管理の基本はスマートポインタですが、かつて std::shared_ptr は単一オブジェクトを指すことしか考慮されていませんでした。そのため、配列を扱うには std::unique_ptr を使うか、std::vector を使うのが定石でした。しかし、所有権を共有しつつ動的配列を扱いたいケースでは、独自にデリータ(deleter)を指定する必要があり、コードが冗長になるという課題がありました。C++17からは std::shared_ptr が配列型をネイティブにサポートしたことで、この問題がスマートに解決されました。

基礎知識: スマートポインタとデリータの仕組み

std::shared_ptr は、内部で参照カウントを持ち、カウントがゼロになった時に保持しているポインタを delete する仕組みです。従来の std::shared_ptr に配列を渡すと、内部では delete T が呼び出されますが、配列の場合は delete[] T を呼び出す必要があります。C++17より前の環境では、これを防ぐためにカスタムデリータ(delete[] を呼ぶラムダ式など)をコンストラクタに渡す必要がありました。C++17ではテンプレートの特殊化により、std::shared_ptr と記述するだけで、自動的に適切な破棄処理が行われるようになりました。

実装/解決策: std::shared_ptr の利用

実装は非常にシンプルです。テンプレート引数に配列型 T[] を指定し、new T[] で確保したメモリを渡すだけです。また、これまでの std::shared_ptr と異なり、配列インデックス演算子(operator[])がオーバーロードされているため、ポインタ演算を意識せず、通常の配列のように要素へアクセス可能です。

サンプルプログラム

以下のコードは、C++17以降の環境でコンパイル可能です。

include
include

int main() {
// C++17以降: 配列を管理する shared_ptr の生成
// コンストラクタで new int[5] を指定するだけで、
// 参照カウントが0になった際に自動的に delete[] が呼ばれます。
std::shared_ptr arr(new int[5]{10, 20, 30, 40, 50});

// 配列インデックス演算子 [] が使用可能
for (int i = 0; i < 5; ++i) { std::cout << "Index " << i << ": " << arr[i] << std::endl; } // 別の shared_ptr にコピーしても所有権は共有される std::shared_ptr sharedArr = arr;

std::cout << "共有後の値: " << sharedArr[2] << std::endl; return 0; // ここで参照カウントが0になり、自動的に delete[] が実行される }

応用・注意点: 実務での使い分け

注意点1: メモリ確保の方法
上記のサンプルでは new を直接使用していますが、C++20以降であれば std::make_shared(size) を使用することが推奨されます。これにより、参照カウント用の制御ブロックと配列のメモリを一度のヒープ確保で済ませることができ、メモリの局所性が高まりパフォーマンスが向上します。

注意点2: コンテナとの比較
機能的には std::vector と重複しますが、std::shared_ptr は「サイズが固定である」「所有権を共有しつつ、動的に生成された配列を安全に受け渡したい」という特殊なケースにおいて非常に強力です。単純な配列保持であれば、まずは std::vector を検討し、共有所有権が必要な場合にのみ std::shared_ptr を採用するという設計指針を持つことが、バグを減らす鍵となります。

コメント

タイトルとURLをコピーしました