【C++】shared_ptrの使い方 スマートポインタでメモリリークを防ごう

C++で動的メモリを使う場合newdeleteを使って管理しますが、メモリリークや二重解放といった問題が発生しやすくなります。

そこで、登場するのがC++11からの機能である「スマートポインタ」

スマートポインタは、特定のスコープから外れると自動的にメモリを解放してくれるため、メモリ管理の負担が軽減されます。

今回は、複数の場所で同じリソース(メモリ)にを共有して管理できるshared_ptrの使い方を紹介します。

shared_ptrの概要

shared_ptrは、C++のスマートポインタの一種で、複数の場所で同じリソースを共有して管理できます。複数のオブジェクトが同じリソースを指している状況に適していて、参照カウントを使用してリソースの寿命を管理します。

shared_ptrの特徴

  • 参照カウントによるメモリの管理
  • リソースの共有
  • 安全なコピー

産業カウントによるメモリの管理

参照カウントは、リソースがどれだけのshared_ptrによって参照されているかを数えるカウンタです。参照カウントが増減するタイミングは以下のタイミングになっています。

参照カウントが増減するタイミング

  • 増えるタイミングshared_ptrのコピーを作成した時に参照カウントが1増える
  • 減るタイミングshared_ptrがスコープを抜けたり、明示的にリセットされた時に参照カウントが1減る

参照カウントが0になったとき(全てのshared_ptrから参照されなくなったとき)にメモリが自動的に解放されます。

リソースの共有

shared_ptrは、同じリソースを複数のポインタで共有できるので、特定のリソースに対して複数の場所から安全にアクセスすることが可能です。

例えば、クラスAで、あるリソースを管理する責任を持ちつつ、そのリソースを他の場所(クラスBやクラスC、他関数)でも参照したい場合にshared_ptrは適しています。

安全なコピー

shared_ptrはコピーが可能です。同じスマートポインタの一種であるunique_ptrはコピー不可となっていますが、shared_ptrはコピーすることができます。

コピーするたびに参照カウントが増えるため、全てのコピーが有効である限りはメモリは解放されません。複数のshared_ptrで同じリソースを管理しつつ、どれか一つがリソースを解放してしまう心配なく安心して使うことができます。

shared_ptrの使い方

shared_ptrの使い方です。

#include <memory>
#include <iostream>

int main() {
  std::shared_ptr<int> p1(new int(10));    // 所有者は1人

  {
    // shared_ptrのコピー
    std::shared_ptr<int> p2 = p1;    // 所有者が2人

    std::cout << *p2 << std::endl;    // 出力値は、10
  }  // ここでp2がスコープを抜けて、所有者が1人になる(まだリソースは解放されない)

  std::cout << *p1 << std::endl;    // 出力値は、10
}  // ここでp1がスコープを抜けて、所有者が0人になる(リソースが解放される)

make_sharedを使用してshared_ptrを作成する

shared_ptrを構築するヘルパ関数のmake_sharedを使用してshared_ptrを作成する方法です。

#include <memory>
#include <

int main() {
  std::shared_ptr<int> p = std::make_shared<int>(100);

  std::cout << *p << std::endl;    // 出力値は、100
}

make_sharedを使用せずに、shared_ptr<int> p(new int(100))というようにshared_ptrオブジェクトを構築すできるが、以下の2つのメモリ確保が必要なため、効率が悪いです

  • ユーザーによるオブジェクトの生成
  • 内部的な参照カウンタの生成

make_sharedは、内部的にオブジェクトの生成を行うため、オブジェクトの生成と参照カウンタの生成が1つの大きなブロックとしてメモリ確保されるので、より効率的になります。

resetで所有権の放棄、新たな所有権を設定する

resetを使うと、リソースの所有権を放棄し、新たなリソースの所有権を設定できます。

#include <memory>
#include <iostream>

int main() {
  std::shared_ptr<int> p1 = std::make_shared<int>(10);
  std::shared_ptr<int> p2 = p1;

  p1.reset();    // p1が所有権を放棄する、まだp2が所有権を保持しているのでリソースは解放されない

  std::shared_ptr<int> q1 = std::make_shared<int>(100);
  std::shared_ptr<int> q2 = q1;

  q2.reset(new int(50));    // q2が現在保持している所有権を放棄し、新たなリソースの所有権を設定する
                            // この時点で、q1とq2は異なるリソースを所有している
}

明示的に所有権の放棄をしたい場合にresetを使用すると、参照カウントを1減らせます。

shared_ptrまとめ

shared_ptrはC++で使われるスマートポインタの一種で、複数の場所で同じリソースを共有して管理できます。

shared_ptrは以下の特徴があります。

shared_ptrの特徴

  • 共有可能:shared_ptrは一つのリソースに対して複数のshared_ptrで安全に共有できます。
  • 参照カウント:参照カウントを使ってメモリの管理が自動化されているので、終了時に正しくメモリが解放されます
  • コピー可能:複数の場所でリソースを簡単に共有できます。

複数のポインタが同じメモリを参照していた場合、どれか一つのポインタがメモリを解放してしまうと、他ポインタがメモリを参照しようとしたときに既にメモリが解放されていてプログラムがクラッシュする可能性があります。

shared_ptrはこのようなバグを防ぐために、参照カウントを使ってリソースの寿命を管理し、有効なshared_ptrが存在しなくなった場合に自動でメモリの解放をしてくれ、メモリリークを防いでくれます。

スポンサーリンク

  • この記事を書いた人

まさじぃ

ダメプログラマ歴17年です。 プログラミング関連の事や、 自分で使って良かったもの等の紹介をメインにやっています。

-プログラミング
-