Menu

C++のスマートポインタ: unique_ptrとshared_ptrを解説

スマートポインタはヒープメモリを所有し、自動的に解放します。unique_ptrshared_ptrmake_uniquemake_sharedを学び、なぜもうnew/deleteをほとんど書く必要がないのかを理解しましょう。

このページのコードはエディタで実行できます - 編集してすぐに結果を確認できます。

スマートポインタが解決する問題

前のページでは、newでメモリを確保し、deleteで解放しました。これは動作しますが、負担があなたにかかります。すべてのnewには対応するdeleteが必要で、それはコードのすべての経路で、途中で例外が投げられる経路でも同様です。1つでも忘れればメモリリークし、deleteを2回実行すればヒープを壊します。

スマートポインタは、ヒープメモリの寿命をスタック上の通常のオブジェクトに結びつけることでこれを解決します。そのオブジェクトがスコープを抜けると、そのデストラクタがあなたの代わりにdeleteを実行します。例外がスタックを巻き戻しても保証されています。この考え方はRAII(Resource Acquisition Is Initialization)と呼ばれ、スマートポインタは<memory>ヘッダにあります。

*pp->memberは、生ポインタとまったく同じように使えます。違いは、deleteを決して呼ばないことです。それはスマートポインタが行います。

unique_ptr: 1つの所有者、共有なし

unique_ptrは、デフォルトで手を伸ばすべきスマートポインタです。これは_排他的_な所有を表します。ある時点でちょうど1つのunique_ptrだけがオブジェクトを所有し、そのポインタが破棄されると、オブジェクトもそれとともに破棄されます。生ポインタと比べて実行時のオーバーヘッドはゼロです。

make_unique(C++14)で作成します。コンストラクタの引数を受け取り、すぐに使えるポインタを返します。

所有者は1つしか存在できないため、unique_ptrコピーできません。コピーしようとするとコンパイルエラーになります。そしてそのエラーは、2つの所有者がどちらも同じオブジェクトをdeleteしようとすることから言語があなたを守っているのです。

auto a = make_unique<int>(10);
auto b = a;   // error: call to deleted copy constructor of unique_ptr

所有権を別の誰かに渡すには、std::moveで_ムーブ_します。ムーブ後、元のポインタは空になります(nullptrを保持します)。

これがほとんどの場面で欲しいモデルです。常にちょうど1つの明確な所有者がいて、コンパイラがそれを強制します。

shared_ptr: 参照カウントによる共有所有

ときには、プログラムの複数の部分が本当に同じオブジェクトを共有する必要があり、どれが最後に終わるかがどの部分にもわからないことがあります。それがshared_ptrの出番です。これは_参照カウント_を保持します。コピーするたびにカウントが増え、破棄されるたびに減り、カウントがゼロになったときにだけオブジェクトが解放されます。

make_sharedで作成します。

unique_ptrとは異なり、shared_ptrのコピーは問題ありません。それこそが目的です。トレードオフはコストです。参照カウントはヒープ上に保存され、アトミックに(スレッドセーフに)更新されるため、shared_ptrunique_ptrより重くなります。誰が何を所有するかを考えるのを避けるためではなく、所有が本当に共有される場合にだけ手を伸ばしてください。

make_sharedshared_ptr<T>(new T(...))よりも効率的でもあります。オブジェクトと制御ブロックを、2回ではなく1回の確保で割り当てます。

weak_ptrと参照サイクルの解消

shared_ptrには古典的な罠が1つあります。2つのオブジェクトが互いにshared_ptrを保持すると、参照カウントが決してゼロに達せず、どちらも解放されません。スマートポインタを使っているのにメモリリークが起きるのです。

struct Node {
    shared_ptr<Node> next;   // 2つのノードが互いを指すと、
};                           // 互いを永遠に生かし続ける

その対策がweak_ptrです。shared_ptrを所有しない観測者です。参照カウントを増やさないため、オブジェクトを生かし続けることは決してありません。オブジェクトを使うには.lock()を呼びます。オブジェクトがまだ存在していればshared_ptrを、すでに消えていれば空のものを返します。

weak_ptrは「バックポインタ」やキャッシュに使ってください。所有権を主張せずにオブジェクトを参照したいあらゆる場面で役立ちます。

よくある間違いと落とし穴

スマートポインタはメモリのバグのほとんどを取り除きますが、いくつかの罠は残ります。

同じメモリのスマートな所有と生の所有を混在させないでください。 同じ生ポインタから2つのスマートポインタを作ってはいけません。どちらもそれをdeleteしようとします。

int* raw = new int(5);
unique_ptr<int> a(raw);
unique_ptr<int> b(raw);   // 大惨事: 両方が同じ int を削除する(二重解放)

これこそがmake_unique/make_sharedを優先する理由です。誤用できる生ポインタがそもそも存在しません。

unique_ptrはムーブ専用なので、所有権を渡すには値で渡してください。 関数がオブジェクトを_所有_せずに_使う_だけなら、代わりに通常の参照か生のT*を受け取ってください。観測するだけの生ポインタはまったく問題ありません。

void consume(unique_ptr<int> p);   // 所有権を取る(中へムーブする)
void observe(int* p);              // 見るだけ、何も所有しない

デフォルトでshared_ptrに頼らないでください。 自由にコピーできるので魅力的ですが、アトミックな参照カウントは実際のパフォーマンスを犠牲にし、共有所有は寿命を推論しにくくします。デフォルトはunique_ptrにし、本当に複数の所有者が必要なときにだけshared_ptrに格上げしてください。

配列向けのunique_ptrには配列形式が必要です。 make_unique<int[]>(n)delete[]を正しく呼ぶunique_ptr<int[]>を返します。実際には、動的配列にはstd::vectorを優先してください。メモリを代わりに管理し、その上にサイズの追跡まで提供してくれます。

次へ: 文字列

これでメモリ管理を掌握できました。スマートポインタはリークなしにヒープ確保を提供してくれます。あなたが確保して持ち回るもっとも一般的なものの1つはテキストであり、C++は生のchar*バッファよりはるかに安全なツールを提供します。次のページではstd::stringを扱います。どのように自分で伸びるのか、毎日使う操作、そしてなぜ手動のメモリ作業から完全に解放してくれるのかを見ていきます。

よくある質問

C++のスマートポインタとは何ですか?

スマートポインタは<memory>にあるオブジェクト(unique_ptrshared_ptrweak_ptr)で、生ポインタをラップし、スコープを抜けるときにメモリを自動的にdeleteします。手動のdeleteや、それを忘れることで生じるリークなしに、ヒープ確保を行えます。

unique_ptrとshared_ptrの違いは何ですか?

unique_ptrはそのオブジェクトの唯一の所有者です。コピーはできず、ムーブのみが可能で、破棄された瞬間にメモリを解放します。shared_ptrは参照カウントによって共有所有を可能にします。複数のshared_ptrが同じオブジェクトを指すことができ、最後の1つが破棄されたときにだけオブジェクトが解放されます。本当に共有所有が必要な場合を除き、unique_ptrを優先してください。

モダンC++ではmake_uniqueとnewのどちらを使うべきですか?

make_uniquemake_sharedを使ってください。これらは1ステップでオブジェクトを確保してラップするため、スマートポインタに届く前に結果がリークしうる生のnewが存在しません。経験則として、モダンなC++コードベースには裸のnewdeleteはほとんど存在しないはずです。

Coddy programming languages illustration

Coddyでコードを学ぼう

始める