C++でプログラマを悩ませるメモリ管理。
通常は、new
で確保したメモリはdelete
で手動で解放する必要があります。
そこで登場するのが、「C++ スマートポインタ」。
スマートポインタを使用すると、不要になった時に自動的にメモリを解放してくれるため、メモリリークを防ぐことができます。
C++ スマートポインタ概要
C++のスマートポインタは、メモリ管理を自動化するためのクラスで、C++11から標準ライブラリに導入されました。
これにより、手動でのメモリ解放が不要になり、メモリリークやメモリの二重解放といった問題を防ぎ、安全で効率的なプログラムが実現できます。
C++ スマートポインタ
- std::unique_ptr:1つの所有権のみを持つ
- std::shared_ptr:複数のポインタでリソースを共有できる
- std::weak_ptr:循環参照を防ぐために使用される
メモリリークについて
メモリリークとは、使い終わったメモリを解放しないことで、無駄にメモリが占領され続ける問題です。
たとえば、ノートに書いたメモを消さずにどんどん書き続けると、ノートがいっぱいになってしまい、新しいことが書けなくなります。
コンピューターでも同様に、メモリがいっぱいになると新しい処理ができなくなってしまいます。
メモリの二重解放について
メモリの二重解放とは、一度解放したメモリをもう一度解放しようとするミスです。
たとえば、友達に貸したノートを既に返してもらったのに、「もう一度返して」と言うようなものです。嫌われそうですね。
コンピューターでは、メモリが二重に解放されるとエラーが発生し、動作が不安定になってしまいます。
std::unique_ptrについて
std::unique_ptr
は、所有権を唯一保持するスマートポインタです。
std::unique_ptrの特徴①:唯一の所有権
std::unique_ptr
は、リソースの所有権を1つのunique_ptr
だけが持つため、同じリソースを複数のポインタで共有することできません。
そのため、所有権がどこにあるかが明確になり、プログラムの動作が予測しやすくなります。
std::unique_ptrの特徴②:コピー不可
std::unique_ptr
は、リソースの所有権を唯一のポインタが持つため、コピーはできません。
コピーはできませんが、std::move
を使用すると、所有権を別のunique_ptr
に移動することができます。
std::unique_ptrの特徴③:自動解放
std::unique_ptr
がスコープを抜けると、管理しているリソースは自動的に解放されます。
手動でdelete
を実施しなくてもリソースが解放されるので、メモリリークが防げます。
std::unique_ptrの使用方法
std::unique_ptrの使用方法です。
#include <iostream>
#include <memory> // unique_ptrを使用するために必要
int main() {
// unique_ptrを作成する
std::unique_ptr<int> p(new int(100));
std::cout << *p << std::endl; // 出力値は100
// deleteでのメモリ解放不要。スコープを抜けると自動的にメモリが解放される
}
その他の使用方法は、以下のページ参照
std::shared_ptrについて
std::shared_ptr
は、複数のポインタでリソースを共有できるスマートポインタです。
std::shared_ptrの特徴①:共有所有権
std::shared_ptr
は、複数のshared_ptr
が同じリソースを共有して管理します。
リソースを使用する各ポインタが存在している間は、リソースは解放されません。
すべてのshared_ptr
が破棄されると、リソースは自動的に解放されます。
std::shared_ptrの特徴②:参照カウント
std::shared_ptr
は、内部的に参照カウントを保持しています。
shared_ptr
がコピーされるたびに増加し、コピーが破棄されると減少します。
参照カウントが0になったとき、リソースが自動的に解放されます。
std::shared_ptrの特徴③:コピー可能
std::shared_ptr
は、std::unique_ptr
とは違いコピー可能です。
コピーが存在する間はリソースの解放は行われないため、同じリソースを複数の場所で使う場合に便利です。
std::shared_ptrの使用方法
std::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人になる(リソースが解放される)
その他のstd::shared_ptr
の使用方法は以下を参照
std::weak_ptrについて
std::weak_ptr
は、リソースの所有権を持たないスマートポインタです。
std::weak_ptrの特徴①:所有権を持たない
std::weak_ptr
は、参照するリソースの所有権を持たないため、参照カウントに影響を与えません。
shared_ptr
で管理しているリソースをweak_ptr
で参照しても参照カウントは増加しないため、リソースの自動解放を遅らせることなくリソースを参照することができます。
std::weak_ptrの特徴②:循環参照の防止
shared_ptr
同士が互いに参照し合うと参照カウントがゼロにならず、リソースが解放されなくなる「循環参照」が発生します。
ここでweak_ptr
を使用すると、所有権を持たない参照ができるため、循環参照を回避できます。
たとえば、親子関係のオブジェクトで、子オブジェクトが親オブジェクトをweak_ptr
で参照すると、循環参照を防げます。
std::weak_ptrの特徴③:ロック機能
weak_ptr
からリソースを利用するためには、lock
メソッドを使用して一時的にshared_ptr
に変換する必要があります。
lock
メソッドを使用すると、リソースが有効な場合にはshared_ptr
が返され、解放済みの場合はnullptr
が返されます。
std::weak_ptrの使用方法
std::weak_ptrの使用方法です。
#include <iostream>
#include <memory>
int main() {
std::shared_ptr<int> sp = std::make_shared<int>(20);
std::weak_ptr<int> wp = sp; // weak_ptr で shared_ptr を参照
if (auto locked = wp.lock()) { // weak_ptr を shared_ptr にロック
std::cout << *locked << std::endl; // 有効なら20を出力
} else {
std::cout << "リソースは解放されています" << std::endl;
}
sp.reset(); // shared_ptr を解放
if (auto locked = wp.lock()) {
std::cout << *locked << std::endl;
} else {
std::cout << "リソースは解放されています" << std::endl;
}
}
C++ スマートポインタ まとめ
C++のスマートポインタは、メモリ管理を自動化し、手動でのメモリ解放によるメモリリークや二重解放を防ぐことができる便利な機能です。
スマートポインタには、以下の3つがあり、用途によって使い分けるかたちになります。
std::unique_ptr
:所有権を1つのオブジェクトに限定するスマートポインタstd::shared_ptr
:複数の場所で所有権を共有できるスマートポインタstd::weak_ptr
:std::shred_ptr
の循環参照を防ぐためのスマートポインタ
手動によるメモリ管理では、メモリリークや二重解放といった問題が発生しやすく、これらのメモリ関連の不具合は原因究明が難航することも多いため、スマートポインタを使用して自動化しましょう。