std::shared_ptr
『C++』の shared_ptr は、動的に確保したメモリの共有所有権を参照カウントで管理するスマートポインタです。複数の shared_ptr が同一のオブジェクトを指すことができ、最後の所有者がスコープを抜けるとメモリが自動解放されます。単一所有でよい場合は unique_ptr を、循環参照を断ち切る場合は weak_ptr を組み合わせて使います。
構文
// ========================================
// shared_ptr の基本構文
// ========================================
#include <memory>
// make_shared でオブジェクトを生成します(推奨)
// 制御ブロックとオブジェクトを1回のアロケーションで確保するため効率的です
std::shared_ptr<型名> ptr1 = std::make_shared<型名>(コンストラクタ引数);
// コピーすると参照カウントが増えます
std::shared_ptr<型名> ptr2 = ptr1; // 参照カウント: 2
// 参照カウントを確認します
long count = ptr1.use_count(); // 2
// ポインタのように使えます
ptr1->メンバ関数();
*ptr1; // 参照外し
// 管理を手放します(カウントが 0 になれば自動解放)
ptr2.reset(); // 参照カウント: 1 に戻ります
// 生のポインタを取得します(所有権は移動しません)
型名* raw = ptr1.get();
// weak_ptr で弱参照を作ります(参照カウントを増やしません)
std::weak_ptr<型名> wptr = ptr1;
// lock() で shared_ptr に昇格してから使います
if (std::shared_ptr<型名> locked = wptr.lock()) {
locked->メンバ関数(); // 有効なら安全に使えます
}
構文一覧
| 構文・操作 | 概要 |
|---|---|
| std::make_shared<T>(...) | 型 T のオブジェクトを動的確保し shared_ptr を返します。制御ブロックとオブジェクトを1回のアロケーションで生成するため、new より効率的です。 |
| std::shared_ptr<T> p2 = p1 | コピーすると参照カウントが増えます。p1 と p2 が同一オブジェクトを共有します。 |
| p.use_count() | 現在の参照カウント(所有者数)を返します。主にデバッグ目的で使います。 |
| p->member / *p | 管理対象オブジェクトのメンバへアクセスします。通常のポインタと同じ感覚で使えます。 |
| p.get() | 管理している生のポインタを返します。所有権は移動しません。既存の生ポインタ API に渡す場合に使います。 |
| p.reset() | この shared_ptr が管理を手放します。参照カウントが 0 になればオブジェクトが解放されます。引数に新しいポインタを渡して差し替えも可能です。 |
| if (p) | nullptr でなければ true になります。null チェックに使えます。 |
| std::weak_ptr<T> w = p | shared_ptr から弱参照を作ります。参照カウントを増やさないため、循環参照の解消や「監視だけしたい」場面で使います。 |
| w.lock() | weak_ptr から shared_ptr を生成して返します。元オブジェクトが解放済みなら nullptr を返します。 |
| w.expired() | weak_ptr が指すオブジェクトが解放済みか調べます。true なら lock() は nullptr になります。 |
サンプルコード
dragonball_shared_ptr.cpp
// ========================================
// dragonball_shared_ptr.cpp
// ドラゴンボールのZ戦士データを shared_ptr で管理し
// 参照カウント・所有権共有・weak_ptr による
// 循環参照回避を確認するサンプルです
// ========================================
#include <iostream>
#include <memory>
#include <string>
#include <vector>
// ========================================
// Warrior — Z戦士クラスです
// ========================================
class Warrior {
public:
std::string name; // 戦士の名前です
int powerLevel; // 戦闘力です
Warrior(const std::string& name, int power)
: name(name), powerLevel(power) {
std::cout << "[生成] " << name << " を確保しました。" << std::endl;
}
~Warrior() {
std::cout << "[解放] " << name << " のメモリを解放しました。" << std::endl;
}
void showProfile() const {
std::cout << " 戦士: " << name
<< " 戦闘力: " << powerLevel << std::endl;
}
};
// ========================================
// Ally — 仲間クラスです
// 担当戦士への弱参照を持ちます(循環参照を防ぎます)
// ========================================
class Ally {
public:
std::string name; // 仲間の名前です
std::string role; // 役割です
// 担当戦士への弱参照です(shared_ptr ではなく weak_ptr を使います)
// Warrior と Ally が互いに shared_ptr を持つと循環参照になるため
// 片方を weak_ptr にして循環を断ち切ります
std::weak_ptr<Warrior> partner;
Ally(const std::string& name, const std::string& role)
: name(name), role(role) {
std::cout << "[生成] 仲間 " << name << " を確保しました。" << std::endl;
}
~Ally() {
std::cout << "[解放] 仲間 " << name << " のメモリを解放しました。" << std::endl;
}
void showProfile() const {
std::cout << " 仲間: " << name
<< " 役割: " << role << std::endl;
}
// 担当戦士の名前を表示します(weak_ptr を lock() して安全に参照します)
void reportToPartner() const {
if (std::shared_ptr<Warrior> w = partner.lock()) {
std::cout << " " << name << " がパートナーの " << w->name
<< " に報告しました。" << std::endl;
} else {
std::cout << " " << name << ": パートナーはすでに解放済みです。" << std::endl;
}
}
};
int main() {
// ========================================
// 1. make_shared でオブジェクトを生成します
// ========================================
std::cout << "=== 1. make_shared で生成 ===" << std::endl << std::endl;
std::shared_ptr<Warrior> goku =
std::make_shared<Warrior>("孫悟空", 150000000);
std::shared_ptr<Warrior> vegeta =
std::make_shared<Warrior>("ベジータ", 120000000);
std::cout << std::endl;
goku->showProfile();
vegeta->showProfile();
// ========================================
// 2. コピーすると参照カウントが増えます
// ========================================
std::cout << std::endl << "=== 2. shared_ptr のコピーと参照カウント ===" << std::endl << std::endl;
std::cout << " goku の参照カウント(コピー前): "
<< goku.use_count() << std::endl;
{
// 内部スコープでコピーします
std::shared_ptr<Warrior> capsuleRef = goku; // カプセルコーポが参照します
std::shared_ptr<Warrior> lookoutRef = goku; // 神の塔が参照します
std::cout << " goku の参照カウント(共有中): "
<< goku.use_count() << std::endl;
capsuleRef->showProfile();
std::cout << " (内部スコープ終了)" << std::endl;
}
std::cout << " goku の参照カウント(スコープ後): "
<< goku.use_count() << std::endl;
// ========================================
// 3. get() で生のポインタを取得します
// ========================================
std::cout << std::endl << "=== 3. get() で生のポインタを参照 ===" << std::endl << std::endl;
Warrior* rawPtr = goku.get();
std::cout << " rawPtr 経由: ";
rawPtr->showProfile();
std::cout << " goku はまだ有効です: "
<< (goku ? "true" : "false") << std::endl;
// rawPtr を delete してはいけません(shared_ptr が管理しているためです)
// ========================================
// 4. reset() で所有を手放します
// ========================================
std::cout << std::endl << "=== 4. reset() で手放す ===" << std::endl << std::endl;
std::cout << " vegeta の参照カウント(reset 前): "
<< vegeta.use_count() << std::endl;
vegeta.reset(); // vegeta はここで nullptr になります
std::cout << " vegeta の参照カウント(reset 後): "
<< vegeta.use_count() << std::endl;
std::cout << " vegeta は有効か: "
<< (vegeta ? "true" : "false") << std::endl;
// ========================================
// 5. weak_ptr で循環参照を回避します
// ========================================
std::cout << std::endl << "=== 5. weak_ptr による循環参照の回避 ===" << std::endl << std::endl;
std::shared_ptr<Ally> piccolo =
std::make_shared<Ally>("ピッコロ", "師匠");
std::shared_ptr<Ally> krillin =
std::make_shared<Ally>("クリリン", "相棒");
std::cout << std::endl;
piccolo->partner = goku;
krillin->partner = goku;
piccolo->showProfile();
piccolo->reportToPartner();
krillin->showProfile();
krillin->reportToPartner();
std::cout << std::endl;
std::cout << " goku の参照カウント(weak_ptr 設定後): "
<< goku.use_count() << std::endl;
// weak_ptr は参照カウントを増やさないのでカウントは 1 のままです
// ========================================
// 6. weak_ptr が指すオブジェクトを解放して動作を確認します
// ========================================
std::cout << std::endl << "=== 6. 解放後の weak_ptr の動作 ===" << std::endl << std::endl;
std::weak_ptr<Warrior> watcherPtr = goku;
std::cout << " goku 解放前: expired = "
<< (watcherPtr.expired() ? "true" : "false") << std::endl;
goku.reset();
std::cout << std::endl;
std::cout << " goku 解放後: expired = "
<< (watcherPtr.expired() ? "true" : "false") << std::endl;
piccolo->reportToPartner();
krillin->reportToPartner();
// ========================================
// 7. vector<shared_ptr> で複数オブジェクトを管理します
// ========================================
std::cout << std::endl << "=== 7. vector<shared_ptr> で管理 ===" << std::endl << std::endl;
std::vector<std::shared_ptr<Warrior>> zFighters;
zFighters.push_back(std::make_shared<Warrior>("孫悟空", 150000000));
zFighters.push_back(std::make_shared<Warrior>("ベジータ", 120000000));
zFighters.push_back(std::make_shared<Warrior>("トランクス", 90000000));
std::cout << std::endl;
std::cout << " Z戦士ユニット:" << std::endl;
for (int i = 0; i < (int)zFighters.size(); i++) {
zFighters[i]->showProfile();
}
std::cout << std::endl << "--- main 終了。全 shared_ptr が自動解放されます ---" << std::endl;
return 0;
}
g++ -std=c++11 dragonball_shared_ptr.cpp -o dragonball_shared_ptr ./dragonball_shared_ptr === 1. make_shared で生成 === [生成] 孫悟空 を確保しました。 [生成] ベジータ を確保しました。 戦士: 孫悟空 戦闘力: 150000000 戦士: ベジータ 戦闘力: 120000000 === 2. shared_ptr のコピーと参照カウント === goku の参照カウント(コピー前): 1 goku の参照カウント(共有中): 3 戦士: 孫悟空 戦闘力: 150000000 (内部スコープ終了) goku の参照カウント(スコープ後): 1 === 3. get() で生のポインタを参照 === rawPtr 経由: 戦士: 孫悟空 戦闘力: 150000000 goku はまだ有効です: true === 4. reset() で手放す === vegeta の参照カウント(reset 前): 1 [解放] ベジータ のメモリを解放しました。 vegeta の参照カウント(reset 後): 0 vegeta は有効か: false === 5. weak_ptr による循環参照の回避 === [生成] 仲間 ピッコロ を確保しました。 [生成] 仲間 クリリン を確保しました。 仲間: ピッコロ 役割: 師匠 ピッコロ がパートナーの 孫悟空 に報告しました。 仲間: クリリン 役割: 相棒 クリリン がパートナーの 孫悟空 に報告しました。 goku の参照カウント(weak_ptr 設定後): 1 === 6. 解放後の weak_ptr の動作 === goku 解放前: expired = false [解放] 孫悟空 のメモリを解放しました。 goku 解放後: expired = true ピッコロ: パートナーはすでに解放済みです。 クリリン: パートナーはすでに解放済みです。 === 7. vector<shared_ptr> で管理 === [生成] 孫悟空 を確保しました。 [生成] ベジータ を確保しました。 [生成] トランクス を確保しました。 Z戦士ユニット: 戦士: 孫悟空 戦闘力: 150000000 戦士: ベジータ 戦闘力: 120000000 戦士: トランクス 戦闘力: 90000000 --- main 終了。全 shared_ptr が自動解放されます --- [解放] トランクス のメモリを解放しました。 [解放] ベジータ のメモリを解放しました。 [解放] 孫悟空 のメモリを解放しました。 [解放] 仲間 クリリン のメモリを解放しました。 [解放] 仲間 ピッコロ のメモリを解放しました。
よくあるミス: get() で取得した生のポインタを delete する
get() で取得した生のポインタを delete すると、shared_ptr が内部で保持しているオブジェクトが二重解放されて未定義動作が発生します。get() はあくまで既存の生ポインタ API に渡すためのものです。
// NG:
std::shared_ptr<Warrior> goku = std::make_shared<Warrior>("孫悟空", 150000000);
Warrior* raw = goku.get();
delete raw; // 二重解放になります(shared_ptr も後でオブジェクトを解放しようとします)
OK: 生のポインタは読み取り・渡し専用として使い、解放は shared_ptr に任せます。
// OK: Warrior* raw = goku.get(); // 生のポインタで既存APIに渡します raw->showProfile(); // 参照はOKです // delete raw; は絶対に呼ばない
よくあるミス: 同じ生ポインタから複数の shared_ptr を作る
同じ生ポインタから複数の shared_ptr を別々に作ると、それぞれが独立した参照カウントを持ちます。片方のカウントがゼロになるとオブジェクトが解放され、もう一方がダングリングポインタになります。
// NG:
Warrior* raw = new Warrior("ピッコロ", 80000000);
std::shared_ptr<Warrior> sp1(raw); // 参照カウント: 1
std::shared_ptr<Warrior> sp2(raw); // 参照カウント: 1(別の制御ブロック!)
// sp1 が解放されると raw は削除されます。sp2 はダングリングポインタになります
OK: std::make_shared を使うか、既存の shared_ptr からコピーして共有します。
// OK:
std::shared_ptr<Warrior> sp1 = std::make_shared<Warrior>("ピッコロ", 80000000);
std::shared_ptr<Warrior> sp2 = sp1; // 同じ制御ブロックを共有します(参照カウント: 2)
概要
std::shared_ptr は参照カウント方式でオブジェクトの共有所有権を管理するスマートポインタです。コピーするたびに参照カウントが増え、全ての shared_ptr がスコープを外れるかリセットされてカウントが 0 になると、管理対象が自動的に delete されます。生成には std::make_shared を使うと、制御ブロックとオブジェクトを1回のアロケーションで確保できるため、new で直接構築するより効率的です。get() で生のポインタを取り出して既存の API に渡せますが、その生ポインタを delete してはいけません。注意点として、2つの shared_ptr が互いを参照し合う「循環参照」が発生すると参照カウントが永久にゼロにならずメモリリークになります。この問題を解消するのが std::weak_ptr で、参照カウントを増やさずにオブジェクトを参照します。使う際は lock() で shared_ptr に昇格してから利用し、元のオブジェクトが既に解放済みであれば lock() は nullptr を返します。所有者が1人でよい場合は <a href="/tpl_dictionary_rep.php?cat=dictionary-cpp&fl=unique_ptr">unique_ptr</a> を選ぶと軽量で意図が明確になります。
記事の間違いや著作権の侵害等ございましたらお手数ですがこちらまでご連絡頂ければ幸いです。