boost::shared_ptr tutorial

27
勉強会資料:スマートポインタ入門 @NU-Pan 2014/07/10() @NU-Pan 勉強会資料:スマートポインタ入門 2014/07/10() 1 / 27

Upload: nupan

Post on 24-Jul-2015

901 views

Category:

Software


0 download

TRANSCRIPT

Page 1: boost::shared_ptr tutorial

勉強会資料:スマートポインタ入門

@NU-Pan

2014/07/10(木)

@NU-Pan 勉強会資料:スマートポインタ入門 2014/07/10(木) 1 / 27

Page 2: boost::shared_ptr tutorial

生ポインタは便利ポインタの偉大な点

他の変数を指し示せる変数である

演算できる

無効値である NULLが存在する

非常に低コスト(実体は 64bit or 32bitの符号なし整数ですから!)

例えば:線形探索

1 // 線 形 探 索 関 数2 template <typename T>3 T* Search(T* begin , T* end , T value){4 for(T* i=begin; i!=end; ++i){5 if(*i==value){6 return i;7 }8 }9 return NULL;

10 }1112 // こ う 使 う13 double Array [10] = {...};14 double* Result = Search(Array , Array +10, 810);

@NU-Pan 勉強会資料:スマートポインタ入門 2014/07/10(木) 2 / 27

Page 3: boost::shared_ptr tutorial

生ポインタは危ない

1 // 画 像 を 作 る 関 数2 uint8_t* CreateImage(int w, int h, int c){3 uint8_t* Image = new uint8_t[w*h*c];45 uint8_t* Pixel = Image;6 for(int i=0; i<w*h*c; ++i){//<-バ ッ フ ァ オ ー バ ー ラ ン !7 memset(Pixel , 127, c);8 Pixel += c;9 }

1011 return Image;12 }1314 // 画 像 に 何 か の 処 理 を す る15 void SomeProcess(uint8_t* image , int w, int h, int c){16 uint8_t* Buffer = CreateImage(w, h, c);1718 // 何 か の 処 理 を い っ ぱ い 書 く1920 }//<-メ モ リ リ ー ク !

確かに便利だが危険も伴うできればこういうリスクは抱えたくない

@NU-Pan 勉強会資料:スマートポインタ入門 2014/07/10(木) 3 / 27

Page 4: boost::shared_ptr tutorial

生ポインタの扱い方

基本的な方針速度が大事なところではオーバーヘッドの少ない生ポインタ

速度よりも “楽ができること”が大事なところではより安全な別の手段を使う

実行速度と安全性のトレードオフを考えて使いましょうということ

例えば:引数を const参照で置き換え

1 // コ ピ ー 出 来 な い ク ラ ス2 class FileWriter{3 private:4 FileWriter(const FileWriter &);5 SomeClass& operator =(const FileWriter &);6 public:7 // い ろ い ろ8 };9

10 // ポ イ ン タ で も と れ る が 参 照 の 方 が 安 全11 void SomeProcess(FileWriter& writer , ...){12 // な に か 処 理 す る13 }

@NU-Pan 勉強会資料:スマートポインタ入門 2014/07/10(木) 4 / 27

Page 5: boost::shared_ptr tutorial

生ポインタの扱い方

例えば:関数の戻り値を boost.optionalにする

1 // 答 え が 出 な い か も 知 れ な い 小 難 し い 計 算 を す る2 boost::optional <int > SomeProcess (...){3 .4 .5 .6 // 途 中 で 計 算 が 破 綻 し た !7 if (...){8 return boost::optional <int >(); // 明 確 な 無 効 値 を 返 却9 }

10 .11 .12 .13 }1415 // こ う 使 う16 boost::optional <int > Result = SomeProcess (...);17 if(Result ){ // そ の ま ま 真 理 値 と し て 有 効/ 無 効 を 判 別 で き る18 int value = *Result; // 値 は* で と れ る19 }else{20 // 失 敗 し た の で エ ラ ー 処 理21 }

@NU-Pan 勉強会資料:スマートポインタ入門 2014/07/10(木) 5 / 27

Page 6: boost::shared_ptr tutorial

生ポインタの扱い方

例えば:ポインタをクラスで包んでリソース管理?

・・・・・・

“管理”って簡単に言うけど具体的に何するんですか?

@NU-Pan 勉強会資料:スマートポインタ入門 2014/07/10(木) 6 / 27

Page 7: boost::shared_ptr tutorial

リソース管理クラス is 何

“管理”に何を求めているのか?自分で確保したリソースの後始末を “いい感じに”やってほしい

“いい感じ”とは “適切なタイミング”でリソースを解放してくれること

“適切なタイミング”とは大抵の場合はリソースが不要になったタイミング

リソース管理クラスとはつまり生のポインタを格納

ポインタの示すリソースが不要になったら自動で解放してくれる

そんな機能を持ったクラスのこと

そういうクラスのことをスマートポインタという

@NU-Pan 勉強会資料:スマートポインタ入門 2014/07/10(木) 7 / 27

Page 8: boost::shared_ptr tutorial

スマートポインタ

スマートポインタの種類一口にスマートポインタといっても何種類かある

主にコピー/移動に関する動作が違う

それぞれ適用できるシチュエーションが違ってくる

スマートポインタ一覧名前 所有権の移動 move コピーstd::auto ptr ○ × ×std::unique ptr ○ ○ ×boost::scoped ptr × × ×boost::shared ptr × × ○

@NU-Pan 勉強会資料:スマートポインタ入門 2014/07/10(木) 8 / 27

Page 9: boost::shared_ptr tutorial

std::auto ptr

特徴コピーの時に “所有権”(=リソース解放の義務)が移譲される

1つのリソースを指す有効なポインタは1個だけ

使えるんなら std::unique ptrを使うべき

使用例:戻り値にする

1 // リ ソ ー ス を 生 成 す る 関 数2 std::auto_ptr <ResouceClass > CreateResouce (...){3 return std::auto_ptr <ResouceClass >(new ResouceClass (...));4 }56 // リ ソ ー ス に 何 か の 処 理 を す る7 void SomeProcess (...){8 // B u f f e r 1へ所有権を移譲9 std::auto_ptr <ResouceClass > Buffer1 = CreateResouce (...);

1011 // B u f f e r 2へ所有権を移譲 (見 た 目 は コ ピ ー な の に B u f f e r 1は無効! )12 std::auto_ptr <ResouceClass > Buffer2 = Buffer1;1314 // い ろ い ろ 処 理 す る1516 } //<- a u t o _ p t rが自動的に解放してくれる

@NU-Pan 勉強会資料:スマートポインタ入門 2014/07/10(木) 9 / 27

Page 10: boost::shared_ptr tutorial

std::unique ptr

特徴std::auto ptrと大体同じだが “move”ができる

使えるのは C++11から (キーワード:右辺値参照、moveセマンティクス)

使用例:戻り値にする

1 // リ ソ ー ス を 生 成 す る 関 数2 std::unique_ptr <ResouceClass > CreateResouce (...){3 return std::unique_ptr <ResouceClass >(new ResouceClass (...));4 }56 // リ ソ ー ス に 何 か の 処 理 を す る7 void SomeProcess (...){8 // B u f f e r 1へ所有権を移譲 ( moveする )9 std:: unique_ptr <ResouceClass > Buffer1 = CreateResouce (...);

1011 // コ ピ ー 禁 止 な の で こ れ は で き な い -> 無 効 な B u f f e r 1は発生しない12 //std::unique_ptr <ResouceClass > Buffer2 = Buffer1;1314 // い ろ い ろ 処 理 す る1516 } //<- u n i q u e _ p t rが自動的に解放してくれる

@NU-Pan 勉強会資料:スマートポインタ入門 2014/07/10(木) 10 / 27

Page 11: boost::shared_ptr tutorial

boost::scoped ptr

特徴コピーも移動も出来ない (禁止されている)

あるスコープでのみ有効なポインタとして使える

使用例:例外によるメモリリーク対策

1 class SomeClass{2 private:3 ...4 public:5 SomeClass (...){6 boost ::scoped_ptr <ResouceClass > Buffer(new ResouceClass (...));78 // 例 外 を 投 げ る か も 知 れ な い 危 な い 関 数 を 呼 ぶ9 DangerFunction (...); //<-例 外 が 起 き た !

10 }11 };12 /*!13 1 . 例 外 が t h r o wされる14 2 . コ ン ス ト ラ ク タ を 抜 け よ う と す る15 3 . コ ン ス ト ラ ク タ を 抜 け る 時 に 変 数 B u f f e rが削除される16 4 . n e wした R e s o u c e C l a s sのオブジェクトが開放される17 5 . リ ソ ー ス 漏 れ を 起 こ さ ず に コ ン ス ト ラ ク タ を 抜 け る18 */

@NU-Pan 勉強会資料:スマートポインタ入門 2014/07/10(木) 11 / 27

Page 12: boost::shared_ptr tutorial

boost::shared ptr

特徴1つのリソースの所有権をみんなで共有しましょうというポインタ

最後に所有権を手放した人が責任を持ってリソース解放

shared ptrの存在=所有権

何がすごいのか?shared ptrが存在する間はリソースの寿命が保証される

コピーができるので STLコンテナに入れることができる

とにかくいろんな問題が解決する

そのほかいろいろすごい(後述)

@NU-Pan 勉強会資料:スマートポインタ入門 2014/07/10(木) 12 / 27

Page 13: boost::shared_ptr tutorial

すごいぞつよいぞ shared ptr

1 // フ ァ ク ト リ 関 数2 boost::shared_ptr <CResouce > CreateResouce (...){3 // そ の ま ま 戻 せ る !4 return boost :: shared_ptr <CResouce >(new CResouce (...));5 }67 // リ ソ ー ス の ポ イ ン タ を 持 つ ク ラ ス8 class CHoge{ // コ ピ ー 禁 止 し な く て 良 い !9 private:

10 boost:: shared_ptr <CResouce > _pResouce;1112 public:13 // コ ン ス ト ラ ク タ で リ ソ ー ス の ポ イ ン タ を 受 け 取 る14 CHoge(const boost :: shared_ptr <CResouce >& p_resouce)15 :_pResouce(p_resouce ){16 }17 };1819 // リ ソ ー ス 利 用 側20 void SomeProcess (...){21 // コ ピ ー 可 な の で コ ン テ ナ に 入 れ る こ と が で き る !22 std::map <std::string , boost ::shared_ptr <CResouce > > ResouceMap;23 ResouceMap["hoge"] = CreateResouce (...);24 }

@NU-Pan 勉強会資料:スマートポインタ入門 2014/07/10(木) 13 / 27

Page 14: boost::shared_ptr tutorial

shared ptrの使い方

1 // 適 当 な ク ラ ス2 class CHoge{3 public:4 void Piyo ();5 };67 typedef boost ::shared_ptr <CHoge > sp_hoge;8 typedef boost ::shared_ptr <const CHoge > scp_hoge;9

10 void f(){11 sp_int pa(new CHoge);1213 // 有 効 な ポ イ ン タ な ら14 if( pa ){15 pa->Piyo ();16 CHoge b = *a;17 scp_hoge c = a;18 CHoge* raw_ptr = a.get ();19 }20 }

@NU-Pan 勉強会資料:スマートポインタ入門 2014/07/10(木) 14 / 27

Page 15: boost::shared_ptr tutorial

shared ptrの実装

参照カウンタ“今時分が所有しているリソースは何人に所有されているか?”を表すカウンタを持っている(参照カウンタ)

正確には参照カウンタへのポインタを持っている

この参照カウンタを上下してリソースを解放すべきか判断する

参照カウンタの動作最初は参照カウンタ=1

shared ptrがコピーされると参照カウンタを+1

shared ptrがデストラクトされると参照カウンタを-1

参照カウンタを-1した時に0になったらリソース解放

@NU-Pan 勉強会資料:スマートポインタ入門 2014/07/10(木) 15 / 27

Page 16: boost::shared_ptr tutorial

shared ptrの動作をトレースしてみる

手順1 リソースを確保2 リソースへのポインタを shred ptrに渡す3 参照数カウント初期化4 shared ptrをコピー5 shared ptrを1つ削除6 shared ptrを0個に7 リソースの解放8 参照カウントの後始末

@NU-Pan 勉強会資料:スマートポインタ入門 2014/07/10(木) 16 / 27

Page 17: boost::shared_ptr tutorial

shared ptrの弱点

便利だけど弱点もある“循環参照”が発生すると正しく解放されない

連結リストの前後の要素を指すポインタには使えないということ

参照数1の shared ptrが残ってしまう

循環参照によるメモリリークのトーレス1 初期状態(循環参照が存在すると最終的にこうなる)2 1つ削除3 さらに1つ削除4 リソースが残ってしまった・・・

@NU-Pan 勉強会資料:スマートポインタ入門 2014/07/10(木) 17 / 27

Page 18: boost::shared_ptr tutorial

循環参照によるリソース漏れの解決

weak ptr

shared ptrの仲間

所有権を持たないのでリソースの寿命には関係しない

指し示すリソースが存在していれば shared ptrに昇格できる

実は参照カウンタとは別に “弱参照カウンタ”というのも存在している

shared ptrへの昇格weak ptrはリソースへのポインタとカウンタへのポインタを持っている

この2つの情報があれば shared ptrを生成できる

弱参照カウンタweak ptrによる参照数のカウンタ

この参照カウンタと弱参照カウンタが両方0になった時カウンタが deleteされる

@NU-Pan 勉強会資料:スマートポインタ入門 2014/07/10(木) 18 / 27

Page 19: boost::shared_ptr tutorial

weak ptrの使い方

1 class CResouce{2 ...3 };45 void f(){6 boost:: shared_ptr <CResouce > sp(new CResouce );7 boost::weak_ptr <CResouce > wp(sp);89 // 参 照 カ ウ ント=1 , 弱 参 照 カ ウ ント=1

1011 boost:: shared_ptr <CResouce > p = wp.lock ();12 if(p){13 // 確 保 成 功 = リ ソ ー ス は ま だ 生 き て い た14 // 参 照 カ ウ ント=2 , 弱 参 照 カ ウ ント=115 }else{16 // 確 保 失 敗 = リ ソ ー ス は す で に 死 ん で い た17 // 参 照 カ ウ ント=0 , 弱 参 照 カ ウ ント=118 }19 }

@NU-Pan 勉強会資料:スマートポインタ入門 2014/07/10(木) 19 / 27

Page 20: boost::shared_ptr tutorial

weak ptrの動作をトレースしてみる

1 初期状態2 weak ptr生成3 shared ptr削除4 weak ptr削除

@NU-Pan 勉強会資料:スマートポインタ入門 2014/07/10(木) 20 / 27

Page 21: boost::shared_ptr tutorial

すごいぞ shared ptr:デリータの指定

デリータとは?shared ptrによって呼び出される解放関数のこと

デフォルトではは deleteが呼び出される

このデリータはコンストラクタで指定できる

つまり delete以外の専用の解放関数が必要なリソースも shared ptrで管理できる(すごい!)

IplImageを shared ptrで管理してみる

1 // I p l I m a g eを s h a r e d _ p t rに包んで生成するファクトリ関数2 boost::shared_ptr <IplImage > CreateIplImage(int w, int h, int c){3 IplImage* pImage = cvCreateImage(cvSize(w, h), IPL_DEPTH_8U , c);4 return boost :: shared_ptr <IplImage >(pImage , &cvReleaseImage );5 }

@NU-Pan 勉強会資料:スマートポインタ入門 2014/07/10(木) 21 / 27

Page 22: boost::shared_ptr tutorial

つよいぞ shared ptr:バイナリを超えても大丈夫

考える状況ライブラリ Aと Bをリンクしてプログラムをコンパイルした

ライブラリ A内で shared ptrを生成した

生成した shared ptrをライブラリ Bで実装されているクラスに渡した

ライブラリ Bでカウントが0になってデリータが呼び出された

もし生ポインタならライブラリ Aの newで生成

ライブラリ Bの deleteで削除ライブラリ Aの newとライブラリ Bの deleteがちゃんと対応しているとは限らない!

▶ ライブラリ Aと Bでリンクしてある mallocの実装が違う▶ リリースビルドとデバッグビルドが混在している

この場合正常に deleteされる保証はない・・・

@NU-Pan 勉強会資料:スマートポインタ入門 2014/07/10(木) 22 / 27

Page 23: boost::shared_ptr tutorial

つよいぞ shared ptr:バイナリを超えても大丈夫

shared ptrなら!ライブラリ Aで shared ptrを生成した時にどの deleteを使うかは決まる

ライブラリ Bでリソースを削除する時にライブラリ Aで設定した deleteが呼び出される!

ちゃんと対応した new/deleteなので OK!

@NU-Pan 勉強会資料:スマートポインタ入門 2014/07/10(木) 23 / 27

Page 24: boost::shared_ptr tutorial

ヤバいぞ shared ptr:マルチスレッドでもOK

マルチスレッドで shared ptr

スレッド Aと Bに1つの shared ptrを渡す(ここのコピーはmutexで保護)

親スレッドで shared ptrを削除

スレッド Aと Bで時間的に同時に shared ptrを削除

正しくリソースが解放される?

atomicなカウンタshared ptrのカウンタは atomicなので時間的に同時にカウンタ操作されても OK!

ただし、一つの shared ptrに同時に読み書きをする場合は mutexで排他制御する必要がある(これは生ポインタでも同じ)

atmicなカウンタの代償カウント操作が重たい!

逐次一貫性の保持のために CPUの最適化に制限がかかるので

@NU-Pan 勉強会資料:スマートポインタ入門 2014/07/10(木) 24 / 27

Page 25: boost::shared_ptr tutorial

スマート配列

配列用 shared ptr

“new []”されたポインタは “delete []”しなければならない

shared ptrでは “delete”が呼び出される

shared arrayなら “delete []”を呼び出してくれる!

添字演算子 “[]”も定義されている

scoped arrayも存在する

ちなみに、バッファがほしい時は std::vectorを使うべきなので scoped arrayの出番は少ない

@NU-Pan 勉強会資料:スマートポインタ入門 2014/07/10(木) 25 / 27

Page 26: boost::shared_ptr tutorial

intrusive ptr

クラス備え付けのカウント機構を使うMicrosoftの COMみたいにクラスにそもそも参照カウント機構が備わっている場合がある

その場合はカウント機構を使いたい・・・

boost::intrusive ptrならクラス備え付けのカウント機構を使ってくれる

アロケーションの効率はよい(カウンタを newしなくていい)が、weak ptrを取れない

@NU-Pan 勉強会資料:スマートポインタ入門 2014/07/10(木) 26 / 27

Page 27: boost::shared_ptr tutorial

まとめ

説明したこと生ポインタは強力だけど危ない

生ポインタは別の手段で置き換える事を考えよう

スマートポインタの使用を検討しよう

shared ptrはとても便利!

大事なことリソースを管理しようと思ったらスマートポインタ

shared ptrはとにかく強力でいろんな状況に適用できる

気をつけることスマートポインタの種類によって解放タイミングや挙動は違う

shared ptrは循環参照が起きないよう注意

@NU-Pan 勉強会資料:スマートポインタ入門 2014/07/10(木) 27 / 27