C++で動的メモリを使う場合new
やdelete
を使って管理しますが、メモリリークや二重解放といった問題が発生しやすくなります。
そこで、登場するのが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
が存在しなくなった場合に自動でメモリの解放をしてくれ、メモリリークを防いでくれます。
その他のスマートポインタについては、以下のリンク参照