Upload
cryolite
View
8.509
Download
1
Embed Size (px)
DESCRIPTION
2009/12/12 Boost.勉強会 プレゼン資料 ppt 第2版. 初版の明らかな不手際を最低限だけ修正. プレゼン発表の録画は http://bit.ly/6yjSkz です.
Citation preview
2009/12/141
shared_ptr & weak_ptrくらいおらいと
http://twitter.com/Cryolite/
2009/12/142
キーワードは所有 (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
2009/12/143
所有とは…
• 所有とは権利だ 俺が所有しているものを勝⼿に捨てるな
• 所有とは義務だ 1回だけ,確実に,事後処理をしろ
2009/12/144
所有を制するものが
C++ を制する
• 所有の権利を主張しないと… 「俺がまだ使っているのに捨てられた!」
=> Dangling pointer
• 所有の義務を果たしていないと… 「誰も使ってないのに⽚付いていない!」
=> Memory leak
2009/12/145
所有の種類と例
• 所有者がただ⼀⼈, 所有者変更不可• 配列とその要素• クラスオブジェクトとメンバ変数• 関数スコープ
(の実⾏) と⾃動変数
• プログラム
(の実⾏) と静的変数• 所有者がただ⼀⼈, 所有者変更可
• std::auto_ptr
• (ムーブセマンティクス)
• 共有
– 所有者が複数• boost::shared_ptr
2009/12/146
共有は難しい
2009/12/147
共有は難しい
私が⽚付けましょう
2009/12/148
共有は難しい
私が⽚付けましょういえいえ,ここは私が⽚付けましょう
2009/12/149
共有は難しい
私が⽚付けましょういえいえ,ここは私が⽚付けましょう
え,ちょ,俺まだ使ってる
2009/12/1410
shared_ptr
– 共有を所有カウントで簡単・安全に扱う
1
共有されるもの
所有カウント
2009/12/1411
shared_ptr
– 共有を所有カウントで簡単・安全に扱う
2
共有されるもの
所有カウント
2009/12/1412
shared_ptr
– 共有を所有カウントで簡単・安全に扱う
3
共有されるもの
所有カウント
2009/12/1413
shared_ptr
– 共有を所有カウントで簡単・安全に扱う
4
共有されるもの
所有カウント
2009/12/1414
shared_ptr
– 共有を所有カウントで簡単・安全に扱う
4
共有されるもの
所有カウント所有カウントが0になったら
片づけを実行
2009/12/1415
所有カウントは所有の権利を保障する
1
共有されるもの
所有カウント
所有の権利:誰かが所有していれば勝手に片付けるな
O.K.
2009/12/1416
所有カウントは所有の義務を履行する
0
共有されるもの
所有カウントが0になれば 後片付け処理を実行
所有の義務:1度だけ, 確実に, 片付け
所有カウント
O.K.
2009/12/1417
shared_ptr
– 共有を所有カウントで簡単・安全に扱う
4
共有されるもの
所有カウント
2009/12/1418
shared_ptr
– 共有を所有カウントで簡単・安全に扱う
4
共有されるもの
所有カウント
shared_ptr shared_ptr shared_ptr shared_ptr
2009/12/1419
shared_ptr
は所有(の義務と権利)とポインタだ
• ポインタとして動作する
shared_ptr C++ の生ポインタと同じように動作
• 所有の権利を保障する
shared_ptr ポインタとして指しているものは生きている
• 所有の義務を履行する
shared_ptr 所有カウントが0になったら関数オブジェクト
(デリータ) を実行
強いポインタ
所有しているものとポインタが
指しているものを一致させる
delete だけじゃない
2009/12/1420
shared_ptr
の基本
{
shared_ptr<int> p(new
int(42));
cout
<< *p << endl;
shared_ptr<int> q = p;
p.reset();
cout
<< *q << endl;
}
2009/12/1421
shared_ptr
の基本
{
shared_ptr<int> p(new
int(42));
cout
<< *p << endl;
shared_ptr<int> q = p;
p.reset();
cout
<< *q << endl;
}ポインタとして動作
+
権利を保障
(参照外しが安全)
2009/12/1422
shared_ptr
の基本
{
shared_ptr<int> p(new
int(42));
cout
<< *p << endl;
shared_ptr<int> q = p;
p.reset();
cout
<< *q << endl;
}ポインタとして動作
+
権利を保障
(参照外しが安全)義務を履行
2009/12/1423
shared_ptr
– int
と同程度にスレッド安全
4
共有されるもの
所有カウントカウントの変化は同期保護
shared_ptr shared_ptr shared_ptr shared_ptr
2009/12/1424
所有カウントは循環所有を扱えない
1
1
所有
所有
所有カウントが永遠に非0
2009/12/1425
発表終わり
2009/12/1426
shared_ptr
の重要なデザインゴール
• ポインタと同等の型互換性
/ バイナリ互換性
• 非侵入性
– あなたのクラス定義に変更なし
所有の共有・受け渡しを表現する
標準インタフェイスの確立
所有の共有・受け渡しを表現する
標準インタフェイスの確立
2009/12/1427
オレオレスマートポインタ
void processX(shared_ptr<X> px);
oreore_ptr<X> px(new
X(…));
processX(px); // コンパイルエラー
2009/12/1428
オレオレスマートポインタ
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> >(px));
processX(qx); // O.K.
2009/12/1429
オレオレスマートポインタ
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> >(px));
processX(qx);
「所有」は型に現れない
=
コンストラクタで「所有」が決まる
shared_ptr
の「所有」は型に現れない
ポイント2
ポイント1
2009/12/1430
所有しない
void processX(shared_ptr<X> px);
static X x; // 静的変数
struct
NullDeleter
{
void operator()(void
*){}
};
shared_ptr<X> px(&x, NullDeleter());
processX(px);
2009/12/1431
所有しない
void processX(shared_ptr<X> px);
static X x; // 静的変数
Struct
NullDeleter
{
void operator()(void
*){}
};
shared_ptr<X> px(&x, NullDeleter());
processX(px);
shared_ptr
の「所有」は型に現れない
ポイント1
「所有」は型に現れない
=
コンストラクタで「所有」が決まる
ポイント2
2009/12/1432
バイナリ境界を越える
– 生ポインタの場合
int
main(){
int
*p = new int(42);
f(p);
p = 0;
.....
}
void f(int
*p){
.....
.....
delete p;
}
a.exe (デバッグビルド) b.dll (リリースビルド)
異なるコンパイル設定の
new と
delete の
組み合わせは環境によってはダウト
2009/12/1433
バイナリ境界を越える
– shared_ptr
の場合
int
main(){
shared_ptr<int> p(new
int(42));
f(p);
p.reset();
.....
}
void f(shared_ptr<int> p){
.....
.....
p.reset();
}
a.exe (デバッグビルド) b.dll (リリースビルド)
2009/12/1434
バイナリ境界を越える
– shared_ptr
の場合
int
main(){
shared_ptr<int> p(new
int(42));
f(p);
p.reset();
.....
}
void f(shared_ptr<int> p){
.....
.....
p.reset();
}
a.exe (デバッグビルド) b.dll (リリースビルド)
デバッグビルドの delete をここで設定
2009/12/1435
バイナリ境界を越える
– shared_ptr
の場合
int
main(){
shared_ptr<int> p(new
int(42));
f(p);
p.reset();
.....
}
void f(shared_ptr<int> p){
.....
.....
p.reset();
}
a.exe (デバッグビルド) b.dll (リリースビルド)
デバッグビルドの delete をここで設定デバッグビルドの delete が呼び出される
2009/12/1436
バイナリ境界を越える
– shared_ptr
の場合
int
main(){
shared_ptr<int> p(new
int(42));
f(p);
p.reset();
.....
}
void f(shared_ptr<int> p){
.....
.....
p.reset();
}
a.exe (デバッグビルド) b.dll (リリースビルド)
デバッグビルドの delete が呼び出されるデバッグビルドの delete をここで設定
どの delete が設定されていようが バイナリ互換性を維持
2009/12/1437
所有だけの
shared_ptr
shared_ptr<int> p(new int(42)); // (A)
shared_ptr<void> q = p;p.reset();// 以下, (A) で生成した int は q が所有
2009/12/1438
shared_ptr<void> による遅延解放
// HeavyToDispose
は削除のコスト大
shared_ptr<HeavyToDispose> px(…);
…// ここで削除して処理が止まると困る…px.reset();
2009/12/1439
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() を実行
2009/12/1440
weak_ptr
4, 2
共有されるもの
所有カウント
shared_ptr shared_ptr
shared_ptr shared_ptr
weak_ptr
weak_ptr
弱いカウント
2009/12/1441
weak_ptr
0, 2
共有されるもの
所有カウント
shared_ptr shared_ptr
shared_ptr shared_ptr
weak_ptr
weak_ptr
弱いカウント
2009/12/1442
weak_ptr
0, 2
共有されるもの
所有カウント
weak_ptr
weak_ptr
弱いカウント
所有カウントが0になったら
片づけを実行
2009/12/1443
weak_ptr
0, 2所有カウント
weak_ptr
weak_ptr
弱いカウント
2009/12/1444
weak_ptr
0, 0所有カウント
weak_ptr
weak_ptr
弱いカウント
2009/12/1445
weak_ptr
0, 0
weak_ptr
weak_ptr
弱いカウントが0になったら
カウントオブジェクトを片付け
2009/12/1446
weak_ptr
にできること
– その1
4, 2
共有されるもの
所有カウント
shared_ptr shared_ptr
shared_ptr shared_ptr
weak_ptr
weak_ptr
弱いカウント
「所有カウントが0かどうか?」に答える
weak_ptr::expired
== false
2009/12/1447
weak_ptr
にできること
– その1
0, 2所有カウント
weak_ptr
weak_ptr
弱いカウント
「所有カウントが0かどうか?」に答える
weak_ptr::expired
== true
2009/12/1448
weak_ptr
にできること
– その1
0, 2所有カウント
weak_ptr
weak_ptr
弱いカウント
「所有カウントが0かどうか?」に答える
=
「対象が死んでいるかどうか?」に答える
2009/12/1449
weak_ptr
にできること
– その2
1, 2
共有されるもの
所有カウント
shared_ptr
weak_ptr
weak_ptr
弱いカウント
対象が生きていたら,それを
所有する
shared_ptr
を作り出せる
2009/12/1450
weak_ptr
にできること
– その2
2, 2
共有されるもの
所有カウント
shared_ptr
weak_ptr
weak_ptr
弱いカウント
shared_ptr
対象が生きていたら,それを
所有する
shared_ptr
を作り出せる
2009/12/1451
weak_ptr
にできること
– その2
0, 2所有カウント
weak_ptr
weak_ptr
弱いカウント
対象が死んでいたら空の
shared_ptr
しか取り出せない
2009/12/1452
weak_ptr
にできること
– その2
0, 2所有カウント
weak_ptr
weak_ptr
弱いカウント
shared_ptr
対象が死んでいたら空の
shared_ptr
しか取り出せない
2009/12/1453
weak_ptr
にできることのまとめ
• 「対象が生きているかどうか?」を答えるプロ クシ
• 対象が生きていたら
shared_ptr
に格上げ可 能
2009/12/1454
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);
2009/12/1455
生ポインタから
shared_ptr
を取得したい
void Framework::onEvent(X
*p)
{
// *p を所有する
shared_ptr
を取りたい
}
2009/12/1456
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();
}
2009/12/1457
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();
}
2009/12/1458
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. 所有が循環しているのでダメ
2009/12/1459
weak_ptr
の使い方
– this への
weak_ptr
struct
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();
}
2009/12/1460
weak_ptr
の使い方
– this への
weak_ptr
struct
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
2009/12/1461
Observer (Publisher/Subscriber)
2009/12/1462
Observer でよくある事故
2009/12/1463
Observer でよくある事故
死んだオブジェクトにアクセス
2009/12/1464
安全な
Observer
• 死んだオブジェクトにイベントを通知しない
• イベント通知中に
subscriber を解放させない
2009/12/1465
安全な
Observer
void Publisher::subscribe(function<void ()> call_back,
weak_ptr<void> wp);
shared_ptr<Subscriber1> p…Publisher::subscribe(
bind(&Subscriber1::notifyEvent, p.get()), p);
2009/12/1466
安全な
Observer
weak_ptr<void> wp…; // subscriber への
weak_ptr
if (shared_ptr<void> p = wp.lock()) {
// 生きているときだけ通知
+ call_back
中だけ所有
call_back();
}
2009/12/1467
安全な
Observer
…ということが
Boost.Signal2 で出来ます
鍵は weak_ptr<void> & shared_ptr<void>
2009/12/1468
オブジェクト間グローバルマッピング
a
b
c
share_ptr
share_ptr
share_ptr share_ptr
2009/12/1469
オブジェクト間グローバルマッピング
a
b
c
map<void *, Y> g_map; // グローバル
y1
y2
y3
share_ptr
share_ptr
share_ptr share_ptr
2009/12/1470
オブジェクト間グローバルマッピング
a
b
c
y1
y2
y3
share_ptr
share_ptr share_ptr
share_ptr
c が消えると…
map<void *, Y> g_map; // グローバル
2009/12/1471
オブジェクト間グローバルマッピング
a
b
c
y1
y2
y3
share_ptr
share_ptr share_ptr
share_ptr
c が消えると… 無駄なマップエントリ
(デッドマップ) が発生
map<void *, Y> g_map; // グローバル
2009/12/1472
策1. キーを
weak_ptr
にして適度にクリーンアップ
a
b
c
map<weak_ptr<void>, Y> g_map;
y1
y2
y3
share_ptr
share_ptr
2009/12/1473
策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.end();) {
if (i->first.expired()) g_map.erase(i++);
else ++i;
}
2009/12/1474
策1. キーを
weak_ptr
にして適度にクリーンアップ
a
b
c
map<weak_ptr<void>, Y> g_map;
y1
y2
y3
share_ptr
share_ptr
デッドマップが適度に解消される
2009/12/1475
策2. マップエントリも所有する
a
b
c
y1
y2
y3
share_ptr
share_ptr
share_ptr share_ptr
map<void *, Y>
2009/12/1476
策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(…)));
2009/12/1477
策2. マップエントリも所有する
a
b
c
map<void *, Y>
y1
y2
y3
share_ptr
share_ptr
share_ptr share_ptr
c を所有する
shared_ptr
がなくなると…
2009/12/1478
策2. マップエントリも所有する
a
b
c
map<void *, Y>
y1
y2
y3
share_ptr
share_ptr
share_ptr share_ptr
c を所有する
shared_ptr
がなくなると…
c をキーとするエントリも自動で
erase
2009/12/1479
循環所有を何とかする
// こういうことがしたい
shared_ptr<X> px
= …;
shared_ptr<Y> py
= …;
pyy
= px->getSharedY(); // X が
Y を所有?
assert(pyy
== py);
pxx
= py->getSharedX(); // Y が
X を所有?
assert(pxx
== px);
2009/12/1480
循環所有を何とかする
// こういうことがしたい
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
所有
所有
?
2009/12/1481
循環所有を何とかする
// こういうことがしたい
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
所有 所有
2009/12/1482
循環所有を何とかする
// こういうことがしたい
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>
2009/12/1483
循環所有を何とかする
// こういうことがしたい
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>
ポイント
2009/12/1484
まとめ
• C++ では所有が重要
• shared_ptr
は所有者が複数居る場合に– 所有カウントで簡単・安全に動作
– 動的デリータによる高い互換性を維持
• weak_ptr– 「生きているかどうか」を答えるプロキシ
– shared_ptr
に格上げして一時的に所有
• shared_ptr
/ weak_ptr
で楽しい
C++ ライフ