Upload
nupan
View
901
Download
0
Embed Size (px)
Citation preview
勉強会資料:スマートポインタ入門
@NU-Pan
2014/07/10(木)
@NU-Pan 勉強会資料:スマートポインタ入門 2014/07/10(木) 1 / 27
生ポインタは便利ポインタの偉大な点
他の変数を指し示せる変数である
演算できる
無効値である 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
生ポインタは危ない
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
生ポインタの扱い方
基本的な方針速度が大事なところではオーバーヘッドの少ない生ポインタ
速度よりも “楽ができること”が大事なところではより安全な別の手段を使う
実行速度と安全性のトレードオフを考えて使いましょうということ
例えば:引数を 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
生ポインタの扱い方
例えば:関数の戻り値を 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
生ポインタの扱い方
例えば:ポインタをクラスで包んでリソース管理?
・・・・・・
“管理”って簡単に言うけど具体的に何するんですか?
@NU-Pan 勉強会資料:スマートポインタ入門 2014/07/10(木) 6 / 27
リソース管理クラス is 何
“管理”に何を求めているのか?自分で確保したリソースの後始末を “いい感じに”やってほしい
“いい感じ”とは “適切なタイミング”でリソースを解放してくれること
“適切なタイミング”とは大抵の場合はリソースが不要になったタイミング
リソース管理クラスとはつまり生のポインタを格納
ポインタの示すリソースが不要になったら自動で解放してくれる
そんな機能を持ったクラスのこと
そういうクラスのことをスマートポインタという
@NU-Pan 勉強会資料:スマートポインタ入門 2014/07/10(木) 7 / 27
スマートポインタ
スマートポインタの種類一口にスマートポインタといっても何種類かある
主にコピー/移動に関する動作が違う
それぞれ適用できるシチュエーションが違ってくる
スマートポインタ一覧名前 所有権の移動 move コピーstd::auto ptr ○ × ×std::unique ptr ○ ○ ×boost::scoped ptr × × ×boost::shared ptr × × ○
@NU-Pan 勉強会資料:スマートポインタ入門 2014/07/10(木) 8 / 27
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
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
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
boost::shared ptr
特徴1つのリソースの所有権をみんなで共有しましょうというポインタ
最後に所有権を手放した人が責任を持ってリソース解放
shared ptrの存在=所有権
何がすごいのか?shared ptrが存在する間はリソースの寿命が保証される
コピーができるので STLコンテナに入れることができる
とにかくいろんな問題が解決する
そのほかいろいろすごい(後述)
@NU-Pan 勉強会資料:スマートポインタ入門 2014/07/10(木) 12 / 27
すごいぞつよいぞ 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
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
shared ptrの実装
参照カウンタ“今時分が所有しているリソースは何人に所有されているか?”を表すカウンタを持っている(参照カウンタ)
正確には参照カウンタへのポインタを持っている
この参照カウンタを上下してリソースを解放すべきか判断する
参照カウンタの動作最初は参照カウンタ=1
shared ptrがコピーされると参照カウンタを+1
shared ptrがデストラクトされると参照カウンタを-1
参照カウンタを-1した時に0になったらリソース解放
@NU-Pan 勉強会資料:スマートポインタ入門 2014/07/10(木) 15 / 27
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
shared ptrの弱点
便利だけど弱点もある“循環参照”が発生すると正しく解放されない
連結リストの前後の要素を指すポインタには使えないということ
参照数1の shared ptrが残ってしまう
循環参照によるメモリリークのトーレス1 初期状態(循環参照が存在すると最終的にこうなる)2 1つ削除3 さらに1つ削除4 リソースが残ってしまった・・・
@NU-Pan 勉強会資料:スマートポインタ入門 2014/07/10(木) 17 / 27
循環参照によるリソース漏れの解決
weak ptr
shared ptrの仲間
所有権を持たないのでリソースの寿命には関係しない
指し示すリソースが存在していれば shared ptrに昇格できる
実は参照カウンタとは別に “弱参照カウンタ”というのも存在している
shared ptrへの昇格weak ptrはリソースへのポインタとカウンタへのポインタを持っている
この2つの情報があれば shared ptrを生成できる
弱参照カウンタweak ptrによる参照数のカウンタ
この参照カウンタと弱参照カウンタが両方0になった時カウンタが deleteされる
@NU-Pan 勉強会資料:スマートポインタ入門 2014/07/10(木) 18 / 27
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
weak ptrの動作をトレースしてみる
1 初期状態2 weak ptr生成3 shared ptr削除4 weak ptr削除
@NU-Pan 勉強会資料:スマートポインタ入門 2014/07/10(木) 20 / 27
すごいぞ 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
つよいぞ 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
つよいぞ shared ptr:バイナリを超えても大丈夫
shared ptrなら!ライブラリ Aで shared ptrを生成した時にどの deleteを使うかは決まる
ライブラリ Bでリソースを削除する時にライブラリ Aで設定した deleteが呼び出される!
ちゃんと対応した new/deleteなので OK!
@NU-Pan 勉強会資料:スマートポインタ入門 2014/07/10(木) 23 / 27
ヤバいぞ 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
スマート配列
配列用 shared ptr
“new []”されたポインタは “delete []”しなければならない
shared ptrでは “delete”が呼び出される
shared arrayなら “delete []”を呼び出してくれる!
添字演算子 “[]”も定義されている
scoped arrayも存在する
ちなみに、バッファがほしい時は std::vectorを使うべきなので scoped arrayの出番は少ない
@NU-Pan 勉強会資料:スマートポインタ入門 2014/07/10(木) 25 / 27
intrusive ptr
クラス備え付けのカウント機構を使うMicrosoftの COMみたいにクラスにそもそも参照カウント機構が備わっている場合がある
その場合はカウント機構を使いたい・・・
boost::intrusive ptrならクラス備え付けのカウント機構を使ってくれる
アロケーションの効率はよい(カウンタを newしなくていい)が、weak ptrを取れない
@NU-Pan 勉強会資料:スマートポインタ入門 2014/07/10(木) 26 / 27
まとめ
説明したこと生ポインタは強力だけど危ない
生ポインタは別の手段で置き換える事を考えよう
スマートポインタの使用を検討しよう
shared ptrはとても便利!
大事なことリソースを管理しようと思ったらスマートポインタ
shared ptrはとにかく強力でいろんな状況に適用できる
気をつけることスマートポインタの種類によって解放タイミングや挙動は違う
shared ptrは循環参照が起きないよう注意
@NU-Pan 勉強会資料:スマートポインタ入門 2014/07/10(木) 27 / 27