Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

boost::shared_ptr tutorial

2,206 views

Published on

いつぞやの勉強回の資料
TUT Advent calendar(http://www.adventar.org/calendars/639)のネタとしてうp

Published in: Software
  • Be the first to comment

  • Be the first to like this

boost::shared_ptr tutorial

  1. 1. 勉強会資料:スマートポインタ入門 @NU-Pan 2014/07/10(木) @NU-Pan 勉強会資料:スマートポインタ入門2014/07/10(木) 1 / 27
  2. 2. 生ポインタは便利 ポインタの偉大な点 他の変数を指し示せる変数である 演算できる 無効値である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 } 11 12 // こう使う 13 double Array [10] = {...}; 14 double * Result = Search (Array , Array +10 , 810); @NU-Pan 勉強会資料:スマートポインタ入門2014/07/10(木) 2 / 27
  3. 3. 生ポインタは危ない 1 // 画像を作る関数 2 uint8_t * CreateImage (int w, int h, int c){ 3 uint8_t * Image = new uint8_t [w*h*c]; 4 5 uint8_t * Pixel = Image ; 6 for(int i =0; i<w*h*c; ++i){ // <- バッファオーバーラン! 7 memset (Pixel , 127 , c); 8 Pixel += c; 9 } 10 11 return Image ; 12 } 13 14 // 画像に何かの処理をする 15 void SomeProcess ( uint8_t * image , int w, int h, int c){ 16 uint8_t * Buffer = CreateImage (w, h, c); 17 18 // 何かの処理をいっぱい書く 19 20 }// <- メモリリーク! 確かに便利だが危険も伴う できればこういうリスクは抱えたくない @NU-Pan 勉強会資料:スマートポインタ入門2014/07/10(木) 3 / 27
  4. 4. 生ポインタの扱い方 基本的な方針 速度が大事なところではオーバーヘッドの少ない生ポインタ 速度よりも楽ができること" が大事なところではより安全な別の手段を 使う 実行速度と安全性のトレードオフを考えて使いましょうということ 例えば:引数を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
  5. 5. 生ポインタの扱い方 例えば:関数の戻り値をboost.optional にする 1 // 答えが出ないかも知れない小難しい計算をする 2 boost :: optional <int > SomeProcess (...){ 3 . 4 . 5 . 6 // 途中で計算が破綻した! 7 if (...){ 8 return boost :: optional <int >(); // 明確な無効値を返却 9 } 10 . 11 . 12 . 13 } 14 15 // こう使う 16 boost :: optional <int > Result = SomeProcess (...); 17 if( Result ){ // そのまま真理値として有効/ 無効を判別できる 18 int value = * Result ; // 値は* でとれる 19 } else { 20 // 失敗したのでエラー処理 21 } @NU-Pan 勉強会資料:スマートポインタ入門2014/07/10(木) 5 / 27
  6. 6. 生ポインタの扱い方 例えば:ポインタをクラスで包んでリソース管理? ・ ・ ・ ・ ・ ・ 管理"って簡単に言うけど具体的に何するんですか? @NU-Pan 勉強会資料:スマートポインタ入門2014/07/10(木) 6 / 27
  7. 7. リソース管理クラスis 何 管理" に何を求めているのか? 自分で確保したリソースの後始末をいい感じに" やってほしい いい感じ" とは適切なタイミング" でリソースを解放してくれること 適切なタイミング" とは大抵の場合はリソースが不要になったタイミング リソース管理クラスとはつまり 生のポインタを格納 ポインタの示すリソースが不要になったら自動で解放してくれる そんな機能を持ったクラスのこと そういうクラスのことをスマートポインタという @NU-Pan 勉強会資料:スマートポインタ入門2014/07/10(木) 7 / 27
  8. 8. スマートポインタ スマートポインタの種類 一口にスマートポインタといっても何種類かある 主にコピー/移動に関する動作が違う それぞれ適用できるシチュエーションが違ってくる スマートポインタ一覧 名前所有権の移動move コピー std::auto ptr ○ × × std::unique ptr ○ ○ × boost::scoped ptr × × × boost::shared ptr × × ○ @NU-Pan 勉強会資料:スマートポインタ入門2014/07/10(木) 8 / 27
  9. 9. std::auto ptr 特徴 コピーの時に所有権"(=リソース解放の義務)が移譲される 1つのリソースを指す有効なポインタは1個だけ 使えるんならstd::unique ptr を使うべき 使用例:戻り値にする 1 // リソースを生成する関数 2 std :: auto_ptr < ResouceClass > CreateResouce (...){ 3 return std :: auto_ptr < ResouceClass >( new ResouceClass (...)); 4 } 5 6 // リソースに何かの処理をする 7 void SomeProcess (...){ 8 // B u f f e r 1 へ所有権を移譲 9 std :: auto_ptr < ResouceClass > Buffer1 = CreateResouce (...); 10 11 // B u f f e r 2 へ所有権を移譲( 見た目はコピーなのにB u f f e r 1 は無効! ) 12 std :: auto_ptr < ResouceClass > Buffer2 = Buffer1 ; 13 14 // いろいろ処理する 15 16 } // <- a u t o _ p t r が自動的に解放してくれる @NU-Pan 勉強会資料:スマートポインタ入門2014/07/10(木) 9 / 27
  10. 10. 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 } 5 6 // リソースに何かの処理をする 7 void SomeProcess (...){ 8 // B u f f e r 1 へ所有権を移譲( m o v e する) 9 std :: unique_ptr < ResouceClass > Buffer1 = CreateResouce (...); 10 11 // コピー禁止なのでこれはできない-> 無効なB u f f e r 1 は発生しない 12 // std :: unique_ptr < ResouceClass > Buffer2 = Buffer1 ; 13 14 // いろいろ処理する 15 16 } // <- u n i q u e _ p t r が自動的に解放してくれる @NU-Pan 勉強会資料:スマートポインタ入門2014/07/10(木) 10 / 27
  11. 11. boost::scoped ptr 特徴 コピーも移動も出来ない(禁止されている) あるスコープでのみ有効なポインタとして使える 使用例:例外によるメモリリーク対策 1 class SomeClass { 2 private : 3 ... 4 public : 5 SomeClass (...){ 6 boost :: scoped_ptr < ResouceClass > Buffer (new ResouceClass (...)); 7 8 // 例外を投げるかも知れない危ない関数を呼ぶ 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
  12. 12. boost::shared ptr 特徴 1つのリソースの所有権をみんなで共有しましょうというポインタ 最後に所有権を手放した人が責任を持ってリソース解放 shared ptr の存在=所有権 何がすごいのか? shared ptr が存在する間はリソースの寿命が保証される コピーができるのでSTL コンテナに入れることができる とにかくいろんな問題が解決する そのほかいろいろすごい(後述) @NU-Pan 勉強会資料:スマートポインタ入門2014/07/10(木) 12 / 27
  13. 13. すごいぞつよいぞshared ptr 1 // ファクトリ関数 2 boost :: shared_ptr < CResouce > CreateResouce (...){ 3 // そのまま戻せる! 4 return boost :: shared_ptr < CResouce >( new CResouce (...)); 5 } 6 7 // リソースのポインタを持つクラス 8 class CHoge { // コピー禁止しなくて良い! 9 private : 10 boost :: shared_ptr < CResouce > _pResouce ; 11 12 public : 13 // コンストラクタでリソースのポインタを受け取る 14 CHoge ( const boost :: shared_ptr < CResouce >& p_resouce ) 15 : _pResouce ( p_resouce ){ 16 } 17 }; 18 19 // リソース利用側 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
  14. 14. shared ptr の使い方 1 // 適当なクラス 2 class CHoge { 3 public : 4 void Piyo (); 5 }; 6 7 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 ); 12 13 // 有効なポインタなら 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
  15. 15. shared ptr の実装 参照カウンタ 今時分が所有しているリソースは何人に所有されているか?" を表すカウ ンタを持っている(参照カウンタ) 正確には参照カウンタへのポインタを持っている この参照カウンタを上下してリソースを解放すべきか判断する 参照カウンタの動作 最初は参照カウンタ=1 shared ptr がコピーされると参照カウンタを+1 shared ptr がデストラクトされると参照カウンタを-1 参照カウンタを-1した時に0になったらリソース解放 @NU-Pan 勉強会資料:スマートポインタ入門2014/07/10(木) 15 / 27
  16. 16. 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
  17. 17. shared ptr の弱点 便利だけど弱点もある 循環参照" が発生すると正しく解放されない 連結リストの前後の要素を指すポインタには使えないということ 参照数1のshared ptr が残ってしまう 循環参照によるメモリリークのトーレス 1 初期状態(循環参照が存在すると最終的にこうなる) 2 1つ削除 3 さらに1つ削除 4 リソースが残ってしまった・・・ @NU-Pan 勉強会資料:スマートポインタ入門2014/07/10(木) 17 / 27
  18. 18. 循環参照によるリソース漏れの解決 weak ptr shared ptr の仲間 所有権を持たないのでリソースの寿命には関係しない 指し示すリソースが存在していればshared ptr に昇格できる 実は参照カウンタとは別に弱参照カウンタ" というのも存在している shared ptr への昇格 weak ptr はリソースへのポインタとカウンタへのポインタを持っている この2つの情報があればshared ptr を生成できる 弱参照カウンタ weak ptr による参照数のカウンタ この参照カウンタと弱参照カウンタが両方0になった時カウンタがdelete される @NU-Pan 勉強会資料:スマートポインタ入門2014/07/10(木) 18 / 27
  19. 19. weak ptr の使い方 1 class CResouce { 2 ... 3 }; 4 5 void f (){ 6 boost :: shared_ptr < CResouce > sp(new CResouce ); 7 boost :: weak_ptr < CResouce > wp(sp ); 8 9 // 参照カウント=1 , 弱参照カウント=1 10 11 boost :: shared_ptr < CResouce > p = wp. lock (); 12 if(p){ 13 // 確保成功= リソースはまだ生きていた 14 // 参照カウント=2 , 弱参照カウント=1 15 } else { 16 // 確保失敗= リソースはすでに死んでいた 17 // 参照カウント=0 , 弱参照カウント=1 18 } 19 } @NU-Pan 勉強会資料:スマートポインタ入門2014/07/10(木) 19 / 27
  20. 20. weak ptr の動作をトレースしてみる 1 初期状態 2 weak ptr 生成 3 shared ptr 削除 4 weak ptr 削除 @NU-Pan 勉強会資料:スマートポインタ入門2014/07/10(木) 20 / 27
  21. 21. すごいぞ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
  22. 22. つよいぞ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
  23. 23. つよいぞshared ptr:バイナリを超えても大丈夫 shared ptr なら! ライブラリA でshared ptr を生成した時にどのdelete を使うかは決まる ライブラリB でリソースを削除する時にライブラリA で設定したdelete が 呼び出される! ちゃんと対応したnew/delete なのでOK! @NU-Pan 勉強会資料:スマートポインタ入門2014/07/10(木) 23 / 27
  24. 24. ヤバいぞ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
  25. 25. スマート配列 配列用shared ptr new []" されたポインタはdelete []" しなければならない shared ptr ではdelete" が呼び出される shared array ならdelete []" を呼び出してくれる! 添字演算子[]" も定義されている scoped array も存在する ちなみに、バッファがほしい時はstd::vector を使うべきなのでscoped array の出番は少ない @NU-Pan 勉強会資料:スマートポインタ入門2014/07/10(木) 25 / 27
  26. 26. intrusive ptr クラス備え付けのカウント機構を使う Microsoft のCOM みたいにクラスにそもそも参照カウント機構が備わって いる場合がある その場合はカウント機構を使いたい・・・ boost::intrusive ptr ならクラス備え付けのカウント機構を使ってくれる アロケーションの効率はよい(カウンタをnew しなくていい)が、weak ptr を取れない @NU-Pan 勉強会資料:スマートポインタ入門2014/07/10(木) 26 / 27
  27. 27. まとめ 説明したこと 生ポインタは強力だけど危ない 生ポインタは別の手段で置き換える事を考えよう スマートポインタの使用を検討しよう shared ptr はとても便利! 大事なこと リソースを管理しようと思ったらスマートポインタ shared ptr はとにかく強力でいろんな状況に適用できる 気をつけること スマートポインタの種類によって解放タイミングや挙動は違う shared ptr は循環参照が起きないよう注意 @NU-Pan 勉強会資料:スマートポインタ入門2014/07/10(木) 27 / 27

×