06/16/22 1 shared_ptr & weak_p tr くくくくくくく http://twitter.com/Cryolite/
May 31, 2015
04/12/231
shared_ptr & weak_ptrくらいおらいと
http://twitter.com/Cryolite/
04/12/232
キーワードは所有 (ownership)
• “…, so keeping track of an object’s ownership is hard work.” – More Effective C++
• “Observe the canonical exception-safety rules: Always use the “RAII” idiom to resource ownership and management.” – Exceptional C++
• “Containers of raw pointers make manual ownership management tricky, so …” – Modern C++ Design
04/12/233
所有とは…
• 所有とは権利だ俺が所有しているものを勝手に捨てるな
• 所有とは義務だ1 回だけ,確実に,事後処理をしろ
04/12/234
所有を制するものが C++ を制する
• 所有の権利を主張しないと…「俺がまだ使っているのに捨てられた!」
=> Dangling pointer
• 所有の義務を果たしていないと…「誰も使ってないのに片付いていない!」
=> Memory Leak
04/12/235
所有の種類と例• 所有者がただ一人 , 所有者変更不可• 配列とその要素• クラスオブジェクトとメンバ変数• 関数スコープ ( の実行 ) と自動変数• プログラム ( の実行 ) と静的変数
• 所有者がただ一人 , 所有者変更可• std::auto_ptr• ( ムーブセマンティクス )
• 共有 – 所有者が複数• boost::shared_ptr
04/12/236
共有は難しい
04/12/237
共有は難しい
私が片付けましょう
04/12/238
共有は難しい
私が片付けましょういえいえ,ここは
私が片付けましょう
04/12/239
共有は難しい
私が片付けましょういえいえ,ここは
私が片付けましょう
え,ちょ,俺まだ使ってる
04/12/2310
shared_ptr – 共有を所有カウントで簡単・安全に扱う
1
共有されるもの
所有カウント
04/12/2311
shared_ptr – 共有を所有カウントで簡単・安全に扱う
2
共有されるもの
所有カウント
04/12/2312
shared_ptr – 共有を所有カウントで簡単・安全に扱う
3
共有されるもの
所有カウント
04/12/2313
shared_ptr – 共有を所有カウントで簡単・安全に扱う
4
共有されるもの
所有カウント
04/12/2314
shared_ptr – 共有を所有カウントで簡単・安全に扱う
4
共有されるもの
所有カウント所有カウントが 0 になったら片づけを実行
04/12/2315
所有カウントは所有の権利を保障する
1
共有されるもの
所有カウント
所有の権利:誰かが所有していれば勝手に片付けるな
O.K.
04/12/2316
所有カウントは所有の義務を履行する
0
共有されるもの
所有カウントが 0 になれば後片付け処理を実行
所有の義務:1 度だけ , 確実に , 片付け
所有カウント
O.K.
04/12/2317
shared_ptr – 共有を所有カウントで簡単・安全に扱う
4
共有されるもの
所有カウント
04/12/2318
shared_ptr – 共有を所有カウントで簡単・安全に扱う
4
共有されるもの
所有カウント
shared_ptr shared_ptr shared_ptr shared_ptr
04/12/2319
shared_ptr は所有 ( の義務と権利 ) とポインタだ
• ポインタとして動作する shared_ptrC++ の生ポインタと同じように動作
• 所有の権利を保障する shared_ptrポインタとして指しているものは生きている
• 所有の義務を履行する shared_ptr所有カウントが 0 になったら関数オブジェクト ( デリータ ) を実行
強いポインタ所有しているものとポインタが
指しているものを一致させる
delete だけじゃない
04/12/2320
shared_ptr の基本
{ shared_ptr<int> p(new int(42)); cout << *p << endl; shared_ptr<int> q = p; p.reset(); cout << *q << endl;}
04/12/2321
shared_ptr の基本
{ shared_ptr<int> p(new int(42)); cout << *p << endl; shared_ptr<int> q = p; p.reset(); cout << *q << endl;} ポインタとして動作
+権利を保障 ( 参照外しが安全 )
04/12/2322
shared_ptr の基本
{ shared_ptr<int> p(new int(42)); cout << *p << endl; shared_ptr<int> q = p; p.reset(); cout << *q << endl;} ポインタとして動作
+権利を保障 ( 参照外しが安全 )
義務を履行
04/12/2323
shared_ptr – int と同程度にスレッド安全
4
共有されるもの
所有カウントカウントの変化は同期保護
shared_ptr shared_ptr shared_ptr shared_ptr
04/12/2324
所有カウントは循環所有を扱えない
1
1
所有
所有
所有カウントが永遠に非 0
04/12/2325
発表終わり
04/12/2326
shared_ptr の重要なデザインゴール
• ポインタと同等の型互換性 / バイナリ互換性
• 非侵入性 – あなたのクラス定義に変更なし
所有の共有・受け渡しを表現する
標準インタフェイスの確立
所有の共有・受け渡しを表現する
標準インタフェイスの確立
04/12/2327
オレオレスマートポインタvoid processX(shared_ptr<X> px);
oreore_ptr<X> px(new X(…));processX(px); // コンパイルエラー
04/12/2328
オレオレスマートポインタvoid processX(shared_ptr<X> px);
template<class P> struct D { P p_; void operator()(void const *) { p_.reset(); }};
oreore_ptr<X> px(new X(…));shared_ptr<X> qx(px.get(), D<oreore_ptr<X> >(p
x));processX(qx);
04/12/2329
オレオレスマートポインタvoid processX(shared_ptr<X> px);
template<class P> struct D { P p_; void operator()(void const *) { p_.reset(); }};
oreore_ptr<X> px(new X(…));shared_ptr<X> qx(px.get(), D<oreore_ptr<X> >(p
x));processX(qx);
「所有」は型に現れない=
コンストラクタで「所有」が決まる
shared_ptr の「所有」は型に現れない
ポイント 2
ポイント 1
04/12/2330
所有しないvoid processX(shared_ptr<X> px);
static X x; // 静的変数
struct NullDeleter { void operator()(void *){}};
shared_ptr<X> px(&x, NullDeleter());processX(px);
04/12/2331
所有しないvoid processX(shared_ptr<X> px);
static X x; // 静的変数
Struct NullDeleter { void operator()(void *){}};
shared_ptr<X> px(&x, NullDeleter());processX(px);shared_ptr<X> px2(new X());processX(px2);
「所有」は型に現れない=
コンストラクタで「所有」が決まる
shared_ptr の「所有」は型に現れない
ポイント 2
ポイント 1
04/12/2332
バイナリ境界を越える
int main(){ int *p = new int(42); f(p); p = 0; .....}
void f(int *p){ ..... ..... delete p;}
a.exe ( デバッグビルド ) b.dll ( リリースビルド )
異なるコンパイル設定の new と delete の組み合わせは環境によってはダウト
04/12/2333
バイナリ境界を越える
int main(){ shared_ptr<int> p(new int(4
2)); f(p); p.reset(); .....}
void f(shared_ptr<int> p){ ..... ..... p.reset();}
a.exe ( デバッグビルド ) b.dll ( リリースビルド )
04/12/2334
バイナリ境界を越える
int main(){ shared_ptr<int> p(new int(4
2)); f(p); p.reset(); .....}
void f(shared_ptr<int> p){ ..... ..... p.reset();}
a.exe ( デバッグビルド ) b.dll ( リリースビルド )
デバッグビルドの delete をここで設定
04/12/2335
バイナリ境界を越える
int main(){ shared_ptr<int> p(new int(4
2)); f(p); p.reset(); .....}
void f(shared_ptr<int> p){ ..... ..... p.reset();}
a.exe ( デバッグビルド ) b.dll ( リリースビルド )
デバッグビルドの delete をここで設定デバッグビルドの delete が呼び出され
る
04/12/2336
バイナリ境界を越える
int main(){ shared_ptr<int> p(new int(4
2)); f(p); p.reset(); .....}
void f(shared_ptr<int> p){ ..... ..... p.reset();}
a.exe ( デバッグビルド ) b.dll ( リリースビルド )
デバッグビルドの delete が呼び出される
デバッグビルドの delete をここで設定
どの delete が設定されていようがバイナリ互換性を維持
04/12/2337
所有だけの shared_ptr
shared_ptr<int> p(new int(42)); // (A)
shared_ptr<void> q = p;p.reset();// 以下, (A) で生成した int は q が所有
04/12/2338
shared_ptr<void> による遅延解放
// HeavyToDispose は削除のコスト大shared_ptr<HeavyToDispose> px(…);…// ここで削除して処理が止まると困る…px.reset();
04/12/2339
shared_ptr<void> による遅延解放vector<shared_ptr<void> > to_be_disposed;
shared_ptr<HeavyToDispose1> px(…);shared_ptr<HeavyToDispose2> py(…);…// ここで削除して処理が止まると困る…to_be_disposed.push_back(px); px.reset();to_be_disposed.push_back(py); py.reset();…// 適当なタイミング or 別スレッドで// to_be_disposed.clear() を実行
04/12/2340
weak_ptr
4, 2
共有されるもの
所有カウント
shared_ptr shared_ptr
shared_ptr shared_ptrweak_ptr
weak_ptr
弱いカウント
04/12/2341
weak_ptr
0, 2
共有されるもの
所有カウント
shared_ptr shared_ptr
shared_ptr shared_ptrweak_ptr
weak_ptr
弱いカウント
04/12/2342
weak_ptr
0, 2
共有されるもの
所有カウント
weak_ptr
weak_ptr
弱いカウント
所有カウントが 0 になったら片づけを実行
04/12/2343
weak_ptr
0, 2所有カウント
weak_ptr
weak_ptr
弱いカウント
04/12/2344
weak_ptr
0, 0所有カウント
weak_ptr
weak_ptr
弱いカウント
04/12/2345
weak_ptr
0, 0
weak_ptr
weak_ptr
弱いカウントが 0 になったらカウントオブジェクトを片付け
04/12/2346
weak_ptr にできること – その 1
4, 2
共有されるもの
所有カウント
shared_ptr shared_ptr
shared_ptr shared_ptrweak_ptr
weak_ptr
弱いカウント
「所有カウントが 0 かどうか?」に答える
weak_ptr::expired == false
04/12/2347
weak_ptr にできること – その 1
0, 2所有カウント
weak_ptr
weak_ptr
弱いカウント
「所有カウントが 0 かどうか?」に答える
weak_ptr::expired == true
04/12/2348
weak_ptr にできること – その 1
0, 2所有カウント
weak_ptr
weak_ptr
弱いカウント
「所有カウントが 0 かどうか?」に答える=
「対象が死んでいるかどうか?」に答える
04/12/2349
weak_ptr にできること – その 2
1, 2
共有されるもの
所有カウント
shared_ptr
weak_ptrweak_ptr
弱いカウント
対象が生きていたら,それを所有する shared_ptr を作り出せる
04/12/2350
weak_ptr にできること – その 2
2, 2
共有されるもの
所有カウント
shared_ptr
weak_ptrweak_ptr
弱いカウント
shared_ptr
対象が生きていたら,それを所有する shared_ptr を作り出せる
04/12/2351
weak_ptr にできること – その 2
0, 2所有カウント
weak_ptrweak_ptr
弱いカウント
対象が死んでいたら空の shared_ptr しか取り出せない
04/12/2352
weak_ptr にできること – その 2
0, 2所有カウント
weak_ptrweak_ptr
弱いカウント
shared_ptr
対象が死んでいたら空の shared_ptr しか取り出せない
04/12/2353
weak_ptr にできることのまとめ
• 「対象が生きているかどうか?」を答えるプロクシ
• 対象が生きていたら shared_ptr に格上げ可能
04/12/2354
weak_ptr の基本shared_ptr<int> p(new int(42)); // (A)weak_ptr<int> wp = p;cout << wp.expired() << endl; // => “false”shared_ptr<int> q = wp.lock();cout << *q << endl; // => “42”, q も所有p.reset(); q.reset(); // (A) の int を解放cout << wp.expired() << endl; // => “true”shared_ptr<int> r = wp.lock();assert(r == 0);
04/12/2355
生ポインタから shared_ptr を取得したい
void Framework::onEvent(X *p){ // *p を所有する shared_ptr を取りたい}
04/12/2356
this への shared_ptrstruct X { shared_ptr<X> this_; // this への shared_
ptr shared_ptr<X> getShared(){ return this_; }};
void Framework::onEvent(X *p){ shared_ptr<X> px = p->getShared();}
04/12/2357
this への shared_ptr はダメstruct X { shared_ptr<X> this_; // this への shared_
ptr shared_ptr<X> getShared(){ return this_; }};
void Framework::onEvent(X *p){ shared_ptr<X> px = p->getShared();}
04/12/2358
this への shared_ptr はダメstruct X { shared_ptr<X> this_; // this への shared_
ptr shared_ptr<X> getShared(){ return this_; }};
void Framework::onEvent(X *p){ shared_ptr<X> px = p->getShared();}
X
X::shared_ptr this_
クラスオブジェクトはメンバ変数を所有 所有
Q. なぜダメか?
A. 所有が循環しているのでダメ
04/12/2359
weak_ptr の使い方 – this への weak_ptrstruct X { weak_ptr<X> this_; // this への weak_ptr shared_ptr<X> getShared(){ return this_; }};
void Framework::onEvent(X *p){ shared_ptr<X> px = p->getShared();}
04/12/2360
weak_ptr の使い方 – this への weak_ptrstruct X { weak_ptr<X> this_; // this への weak_ptr shared_ptr<X> getShared(){ return this_; }};
void Framework::onEvent(X *p){ shared_ptr<X> px = p->getShared();}
参考: boost::enable_shared_from_this
04/12/2361
Observer (Publisher/Subscriber)
04/12/2362
Observer でよくある事故
04/12/2363
Observer でよくある事故
死んだオブジェクトにアクセス
04/12/2364
安全な Observer
• 死んだオブジェクトにイベントを通知しない• イベント通知中に subscriber を解放させない
04/12/2365
安全な Observer
void Publisher::subscribe(function<void ()> call_back, weak_ptr<void> wp);
shared_ptr<Subscriber1> p…Publisher::subscribe( bind(&Subscriber1::notifyEvent, p.get()), p);
04/12/2366
安全な Observer
weak_ptr<void> wp…; // subscriber への weak_ptrif (shared_ptr<void> p = wp.lock()) { call_back();}
04/12/2367
安全な Observer
… ということが Boost.Signal2 で出来ます鍵は weak_ptr<void> & shared_ptr<void>
04/12/2368
オブジェクト間グローバルマッピング
a
b
c
share_ptr
share_ptr
share_ptr share_ptr
04/12/2369
オブジェクト間グローバルマッピング
a
b
c
map<void *, Y> g_map; // グローバル
y1
y2
y3
share_ptr
share_ptr
share_ptr share_ptr
04/12/2370
オブジェクト間グローバルマッピング
a
b
c
y1
y2
y3
share_ptr
share_ptr share_ptr
share_ptr
c が消えると…
map<void *, Y> g_map; // グローバル
04/12/2371
オブジェクト間グローバルマッピング
a
b
c
y1
y2
y3
share_ptr
share_ptr share_ptr
share_ptr
c が消えると… 無駄なマップエントリ( デッドマップ ) が発生
map<void *, Y> g_map; // グローバル
04/12/2372
策 1. キーを weak_ptr にして適度にクリーンアップ
a
b
c
map<weak_ptr<void>, Y> g_map;
y1
y2
y3
share_ptr
share_ptr
04/12/2373
策 1. キーを weak_ptr にして適度にクリーンアップ
a
b
c
map<weak_ptr<void>, Y> g_map;
y1
y2
y3
share_ptr
share_ptr
// 以下を適度に実行for (auto i = g_map.begin(); i != g_map.en
d();) { if (i->first.expired()) g_map.erase(i++); else ++i;}
04/12/2374
策 1. キーを weak_ptr にして適度にクリーンアップ
a
b
c
map<weak_ptr<void>, Y> g_map;
y1
y2
y3
share_ptr
share_ptr
デッドマップが適度に解消される
04/12/2375
策 2. マップエントリも所有する
a
b
c
y1
y2
y3
share_ptr
share_ptr
share_ptr share_ptr
map<void *, Y>
04/12/2376
策 2. マップエントリも所有する
a
b
c
y1
y2
y3
share_ptr
share_ptr
share_ptr share_ptr
map<void *, Y>
struct D{ map<void *, Y> &m_; void operator()(void *p){ m_.erase(p); delete p; }};
D d(g_map);shared_ptr<C> pc(new C(), D(g_map));g_map.insert(make_pair(pc.get(), Y(…)));
04/12/2377
策 2. マップエントリも所有する
a
b
c
map<void *, Y>
y1
y2
y3
share_ptr
share_ptr
share_ptr share_ptr
c を所有する shared_ptr がなくなると…
04/12/2378
策 2. マップエントリも所有する
a
b
c
unordered_map<void const *, Y>
y1
y2
y3
share_ptr
share_ptr
share_ptr share_ptr
c を所有する shared_ptr がなくなると…
c をキーとするエントリも自動で erase
04/12/2379
循環所有を何とかする// こういうことがしたいshared_ptr<X> px = …;shared_ptr<Y> py = …;
pyy = px->getSharedY(); // X が Y を所有?assert(pyy == py);
pxx = py->getSharedX(); // Y が X を所有?assert(pxx == px);
04/12/2380
循環所有を何とかする// こういうことがしたいshared_ptr<X> px = …;shared_ptr<Y> py = …;
pyy = px->getSharedY(); // X が Y を所有?assert(pyy == py);
pxx = py->getSharedX(); // Y が X を所有?assert(pxx == px);
X Y
所有
所有
?
04/12/2381
循環所有を何とかする// こういうことがしたいshared_ptr<X> px = …;shared_ptr<Y> py = …;
pyy = px->getSharedY(); // X が Y を所有?assert(pyy == py);
pxx = py->getSharedX(); // Y が X を所有?assert(pxx == px);
X Y
shared_ptr
所有 所有
04/12/2382
循環所有を何とかする// こういうことがしたいshared_ptr<X> px = …;shared_ptr<Y> py = …;
pyy = px->getSharedY(); // X が Y を所有?assert(pyy == py);
pxx = py->getSharedX(); // Y が X を所有?assert(pxx == px);
X Y
shared_ptr
所有 所有ポイント
shared_ptr<X>
04/12/2383
循環所有を何とかする// こういうことがしたいshared_ptr<X> px = …;shared_ptr<Y> py = …;
pyy = px->getSharedY(); // X が Y を所有?assert(pyy == py);
pxx = py->getSharedX(); // Y が X を所有?assert(pxx == px);
X Y
shared_ptr
所有 所有
shared_ptr<Y>
ポイント
04/12/2384
循環所有を何とかする// こういうことがしたいshared_ptr<X> px = …;shared_ptr<Y> py = …;
pyy = px->getSharedY(); // X が Y を所有?assert(pyy == py);
pxx = py->getSharedX(); // Y が X を所有?assert(pxx == px);
X Y
shared_ptr
所有 所有
shared_ptr<Y>
ポイント
04/12/2385
まとめ• C++ では所有が重要• shared_ptr は所有者が複数居る場合に
– 所有カウントで簡単・安全に動作– 動的デリータによる高い互換性を維持
• weak_ptr– 「生きているかどうか」を答えるプロキシ– shared_ptr に格上げして一時的に所有
• shared_ptr / weak_ptr で楽しい C++ ライフ