Publicidad
Publicidad

Más contenido relacionado

Presentaciones para ti(20)

Publicidad
Publicidad

イマドキC++erのモテカワリソース管理術

  1. 1
  2. 2014/07/12 イマドキC++erの モテカワリソース管理術! @hotwatermorning C++an C++am(キ++ャンキ++ャン編集部) 2
  3. C++?メモリリークするよね? 3
  4. メモリリーク! 4
  5. メモリの2重解放! 5
  6. バグが取れない 6
  7. 納期に間に合わない 7
  8. 夏を満喫できない…>< 8 モテカワになれない
  9. 夏を満喫できない…>< 9 モテカワになれない イマドキC++erの モテカワリソース管理術を使うと…!?
  10. メモリリークしない!! 10
  11. 二重解放も発生しない!! 11
  12. プログラムの構造もすっきり! 12
  13. 納期に間に合う!! 13
  14. 夏を満喫できる!! 14
  15. 夏を満喫できる!! 15 モテカワになる
  16. 夏を 16
  17. 満喫するための 17
  18. リソース管理術! 18
  19. モテカワリソース管理術解説 19
  20. モテカワリソース管理術解説 20 ✤ メモリリークはなぜ発生するのか ✤ RAIIによるリソース管理 ✤ RAIIでメモリリークを制する ✤ 他の言語のリソース管理と比較
  21. モテカワリソース管理術解説 21 ✤ メモリリークはなぜ発生するのか ✤ RAIIによるリソース管理 ✤ RAIIでメモリリークを制する ✤ 他の言語のリソース管理と比較
  22. メモリリークはなぜ発生するのか 22
  23. メモリリークはなぜ発生するのか 23 ✤ そもそもメモリリークとは? ✤ 動的に確保したメモリ領域の解放し忘れ ✤ メモリリソースの無駄になる ✤ リークが増えれば、メモリリソースが枯渇する ✤ 発生すると、原因の特定が難しいバグ
  24. メモリリークはなぜ発生するのか ✤ C言語やC++は、GC(Garbage Collection)の 仕組みを持っていない ✤ GC : プログラムの実行中に動的に確保したメモリ領域の うち、不要になった部分を自動で解放する仕組み 24
  25. void foo() { // int型10個分の領域を確保 int* numbers = malloc( sizeof( int ) * 10 ); numbers[0] = 100; numbers[5] = 200; numbers[9] = 1000; // 使い終わったら解放 free( numbers ); } mallocとfreeの対応 25
  26. void foo() { // int型10個分の領域を確保 int* numbers = malloc( sizeof( int ) * 10 ); numbers[0] = 100; numbers[5] = 200; numbers[9] = 1000; // 使い終わったのに、解放してない。 free( numbers ); } // mallocで確保した領域はもうだれも知らない // => メモリリーク発生 メモリリーク 26
  27. void foo() { // int型10個分の領域を確保 int* numbers = malloc( sizeof( int ) * 10 ); numbers[0] = 100; numbers[5] = 200; numbers[9] = 1000; // 使い終わったら解放 free( numbers ); // すでに解放した領域にアクセスした // => Undefined behavior numbers[0] = 101; } ダングリングポインタ 27
  28. void foo() { // int型10個分の領域を確保 int* numbers = malloc( sizeof( int ) * 10 ); numbers[0] = 100; numbers[5] = 200; numbers[9] = 1000; // 使い終わったら解放 free( numbers ); // 解放した領域を再度解放しようとした // => Undefined behavior free( numbers ); } 2重開放 28
  29. C言語のメモリ管理 29 ✤ malloc()で動的に確保したメモリ領域は、 free()で確実に一度だけ解放する必要がある ✤ ポインタ変数は、そのメモリ領域を指すだけで、 その値の有効性や管理の仕方については知らない ✤ メモリ管理は完全にプログラマに任されている
  30. void readFileAndOutput() { const char* file_name = "input.txt"; // ファイルからとあるデータの配列を読み込む FileData* file_data = loadFileData( file_name ); for( int i = 0; i < file_data->length_; i++ ) { outputData( file_data->elements_[i] ); } // 使い終わったら解放する cleanupFileData( file_data ); } メモリ管理の複雑な例 30
  31. // データを保持する構造体 typedef struct FileData_tag { // データ数 int length_; // データ列 DataElement *elements_; } FileData; メモリ管理の複雑な例 31
  32. // ファイルからデータの配列を読み込んで返す FileData* loadFileData( const char* file_name ) { // FileData型のオブジェクトを確保する FileData* file_data = malloc( sizeof( FileData ) ); memset( file_data, 0, sizeof( FileData ) ); // ファイルの内容をfile_dataに読み込む parseFile( file_name, file_data ); // 読み込んだデータを返す return file_data; } メモリ管理の複雑な例 32
  33. // 確保したFileData型のオブジェクトを解放する void cleanupFileData( FileData* file_data ) { free( file_data ); } メモリ管理の複雑な例 33 このコードには怪しい臭いが漂っている。 loadFileData関数で確保した領域を cleanupFileDataで正しく開放しているように 見えるが・・・ 明らかにそれ以外に確保している領域があるはず。
  34. // ファイルからデータの配列を読み込んで返す FileData* loadFileData( const char* file_name ) { // FileData型のオブジェクトを確保する FileData* file_data = malloc( sizeof( FileData ) ); memset( file_data, 0, sizeof( FileData ) ); // ファイルの内容をfile_dataに読み込む parseFile( file_name, file_data ); // 読み込んだデータを返す return file_data; } メモリ管理の複雑な例 34
  35. // ファイルをオープンして、各行のデータを読み込む void parseFile( const char* file_name, FileData* file_data) { FILE* file = fopen( file_name ); fscanf( file, "%d", &file_data->length_ ); // 行数分のメモリ領域を確保 file_data->elements_ = malloc( sizeof( DataElement ) * file_data->length_ ); //すべての行のデータを読み込み parseElements( file, file_data->length_, file_data->elements_ ); fclose( file ); } 内部で確保した領域 35
  36. // ファイルをオープンして、各行のデータを読み込む void parseFile( char* file_name, FileData* file_data) { FILE* file = fopen( file_name ); fscanf( file, "%d", &file_data->length_ ); // 行数分のメモリ領域を確保 file_data->elements_ = malloc( sizeof( DataElement ) * file_data->length_ ); //すべての行のデータを読み込み parseElements( file, file_data->length_, file_data->elements_ ); fclose( file ); } 内部で確保した領域 36
  37. // 確保したFileData型のオブジェクトを解放する void cleanupFileData( FileData* file_data ) { free( file_data ); } メモリ管理の複雑な例 37 file_dataが内部で持ってる DataElementの配列を解放していない。 -> メモリリーク発生
  38. // 確保したFileData型のオブジェクトを解放する void cleanupFileData( FileData* file_data ) { // データを保持する配列が確保されていたら if( file_data->elements_ ) { //解放する free( file_data->elements_ ); } free( file_data ); } 修正版 38
  39. メモリ管理の責任 39 ✤ C言語では、プログラマが明示的にメモリの確保 と解放を対応させる責任を持つ ✤ これはGC付きの言語との大きく異なる点 ✤ さもなければメモリリークが発生 ✤ なんらかのクリーンアップ用の関数によって確保 /解放の対応付けを集約できたとしても、結局 loadFileData()とcleanupFileData()を対応させ るような責任がプログラマにある
  40. public static void foo() { // 動的に確保したメモリ領域 int[] numbers = new int[10]; numbers[0] = 100; numbers[5] = 200; numbers[9] = 1000; // 解放の処理は書かなくていい // プログラム上でメモリ領域が不要になったら // GCが適当なタイミングでその領域を解放する。 } GC付き言語の例(Java) 40
  41. GC付き言語のメモリ管理 41 ✤ GC付き言語では、確保したメモリ対する解放処 理はGCによって自動的に行われるので、プログ ラマは解放の責任を負わない
  42. C++の場合 ✤ C++にはGCが無いため、C言語と同様に、 動的に確保したメモリ領域は自動的に解放され ない 42
  43. void foo() { // int型10個分の領域を確保 int* numbers = new int[10]; numbers[0] = 100; numbers[5] = 200; numbers[9] = 1000; // 使い終わったらその領域を解放 delete [] numbers; } C++の場合 43
  44. void foo() { // int型10個分の領域を確保 int* numbers = new int[10]; numbers[0] = 100; numbers[5] = 200; numbers[9] = 1000; // 解放処理を忘れると => メモリリーク delete [] numbers; } // ダングリングポインタや2重解放もC言語と同様 C++の場合 44
  45. C++の場合 45 ✤ ただしC++には、動的にメモリ領域を扱うための クラスが用意されている ✤ std::vector, std::list, std::deque, etc, ...
  46. void foo() { // int型10個分の領域を持つ // vector<int>のオブジェクトを構築する std::vector<int> numbers( 10 ); numbers[0] = 100; numbers[5] = 200; numbers[9] = 1000; } // vectorクラスで確保したメモリは自動で解放される // ・・・どうやって? vectorクラス 46
  47. void foo() { // オブジェクト作成 => コンストラクタの呼び出し std::vector<int> numbers( 10 ); numbers[0] = 100; numbers[5] = 200; numbers[9] = 1000; } // スコープから抜ける => デストラクタの呼び出し コンストラクタ/デストラクタ 47
  48. // std::vectorクラスの定義のイメージ template<class T> class vector { private: T *data_; public: // コンストラクタ vector( size_t capacity ) { data_ = new T[capacity]; } クラスによるメモリ管理 48
  49. // デストラクタ ~vector() { delete [] data_; } // アクセッサ T& operator[]( size_t index ) { return data_[index]; } }; クラスによるメモリ管理 49
  50. void foo() { // 変数の定義とコンストラクタ呼び出し std::vector<int> numbers; numbers.vector( 10 ); // 指定サイズでメモリを確保 numbers[0] = 100; numbers[5] = 200; numbers[9] = 1000; // 変数の破棄時にデストラクタの呼び出し numbers.~vector(); // 確保したメモリ領域を解放 } 実行イメージ(擬似コード) 50
  51. void foo() { // int型10個分の領域を持つ // vector<int>のオブジェクトを構築する std::vector<int> numbers( 10 ); numbers[0] = 100; numbers[5] = 200; numbers[9] = 1000; } クラスによるメモリ管理 51 クラスがメモリを管理しているのでプログラマが メモリ管理を意識する必要がない
  52. C++でのメモリ管理 52 ✤ C++ではクラスの仕組みの中に、メモリ管理を 隠 できる
  53. モテカワリソース管理術解説 53 ✤ メモリリークはなぜ発生するのか ✤ RAIIによるリソース管理 ✤ RAIIでメモリリークを制する ✤ 他の言語のリソース管理と比較
  54. メモリ以外のリソース 54 ✤ クラスの仕組みで管理できるリソースはメモリ だけではない ✤ ファイル ✤ Mutex ✤ その他、Socket, DBコネクション, etc, ...
  55. void foo() { // ファイルをオープン std::fstream file( "output.txt" ); // ファイルに書き込み file << "hello world." << std::endl; } // スコープを抜けるとファイルを自動でcloseする ファイルの例 55 ファイル(ファイルハンドル)というリソースを、 std::fstreamというクラスの中で管理している
  56. std::mutex g_mutex; void thread_proc( AwesomeSharedData* d ) {   // ミューテックスのロックを取得 std::lock_guard<std::mutex> lock( g_mutex ); ModifySharedData( d ); } // ミューテックスのロックを解放 Mutexの例 56 ミューテックスのロック状態というリソースを、 std::lock_guardというクラスの中で管理している
  57. std::mutex g_mutex; // lock()/unlock()という           // メンバ関数を持つ void thread_proc( AwesomeSharedData* d ) { std::lock_guard<std::mutex> lock( g_mutex ); ModifySharedData( d ); } Mutexの例 57
  58. template<class MutexType> class lock_guard { private: MutexType *m_; public: // コンストラクタ lock_guard( MutexType& m ) : m_( &m ) { m_->lock(); } // デストラクタ ~lock_guard() { m_->unlock(); } }; クラス定義のイメージ 58
  59. RAII 59 ✤ このように、なんらかのリソースの確保/解放を オブジェクトの寿命に紐付けて、リソースを管理 する手法(idiom) ✤ 「あーる・えー・あい・あい」 ✤ Resource Acquisition Is Initialization ✤ 三つの利点(カプセル化、例外安全性、局所性)
  60. RAII 60 ✤ このように、なんらかのリソースの確保/解放を オブジェクトの寿命に紐付けて、リソースを管理 する手法(idiom) ✤ 「あーる・えー・あい・あい」 ✤ Resource Acquisition Is Initialization ✤ 三つの利点(カプセル化、例外安全性、局所性)
  61. std::mutex g_mutex; void thread_proc( AwesomeSharedData* d ) { std::lock_guard<std::mutex> lock( g_mutex ); ModifySharedData( d ); } Mutexの例 61 ロック状態というリソースを取得したい => そのためのオブジェクトを作って初期化する
  62. RAII 62 ✤ スマートポインタクラスのように、 とあるリソースの確保/解放をオブジェクトの 寿命に紐付けて、リソースを管理する方法 ✤ Resource Acquisition Is Initialization ✤ 「あーる・えー・あい・あい」 ✤ 三つの利点(カプセル化、例外安全性、局所性)
  63. カプセル化 ✤ さまざまなリソースを、オブジェクトの寿命とい う形で、同じように管理できる ✤ リソース毎に異なる管理方法の詳細を知る必要が ない ✤ ファイル : open / close ✤ ミューテックス : lock / unlock ✤ メモリ : new / delete 63
  64. 例外安全性 ✤ 正常系の動作でも、例外発生時にも、同じように リソースの解放処理が行われる ✤ 例外が発生したことによって、プログラムが不正 な状態になってしまうことがない ✤ 参考 : 「Exceptional C++―47のクイズ形式に よるプログラム問題と解法 (C++ in-Depth Series)」(必読) 64
  65. std::mutex g_mutex; void thread_proc( AwesomeSharedData* d ) { // 直接lock()/unlock()する g_mutex.lock(); ModifySharedData( d ); g_mutex.unlock(); } 例外安全ではない例 65
  66. std::mutex g_mutex; void thread_proc( AwesomeSharedData* d ) { // 直接lock()/unlock()する g_mutex.lock(); // この関数が例外を投げると ModifySharedData( d ); // ミューテックスがロックされたままになる g_mutex.unlock(); } 例外安全ではない例 66
  67. std::mutex g_mutex; void thread_proc( AwesomeSharedData* d ) { std::lock_guard<std::mutex> lock( g_mutex ); // この関数が例外を投げても ModifySharedData( d ); } // その例外がさらに外に送出される際に変数`lock`の // デストラクタが呼ばれる 例外安全になる 67 RAIIによって、 unlock()が呼ばれるのを保証できる
  68. 局所性 ✤ RAIIはクラス定義にリソース確保と解放の処理を 記述する ✤ リソースを使用する側のコードがプログラム中に 散らばらない ✤ そのため、リソース管理のコードの局所性が高い 68
  69. (余談)これからのRAII ✤ RAIIのクラスを簡単に定義するためのラッパーク ラスが提案されている ✤ http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3677.html ✤ まだ標準に取り込まれてないが、ライブラリ実装 なので、自分でも定義できる ✤ リソース管理がより簡単になる 69
  70. モテカワリソース管理術解説 70 ✤ メモリリークはなぜ発生するのか ✤ RAIIによるリソース管理 ✤ RAIIでメモリリークを制する ✤ 他の言語のリソース管理と比較
  71. スマートポインタ 71 ✤ C++ではRAIIによって、クラスのオブジェクトで メモリを管理できる ✤ さらに、C++には演算子オーバーロードによって ポインタのように振舞うクラスが定義できる ✤ 演算子オーバーロード : 演算子の機能を再定義できる仕組み
  72. void foo() { // 生のポインタを受け取って中で管理するクラス std::unique_ptr<AwesomeClass> p( new AwesomeClass ); // 通常のポインタのように使用できる p->MemberFunction(); p->member_variable_; } ポインタのように振舞う 72
  73. void foo() { // 生のポインタを受け取って中で管理するクラス std::unique_ptr<AwesomeClass> p( new AwesomeClass ); // 通常のポインタのように使用できる p->MemberFunction(); p->member_variable_; // pが生きているときは // 内部で管理しているポインタが有効 } // オブジェクトがデストラクトされるときに // 自動でdeleteを呼び出すのでリークしない メモリを管理する 73
  74. template<class T> class unique_ptr { private: T *p_; public: // コンストラクタ unique_ptr( T* p ) : p_( p ) {} unique_ptr ( const unique_ptr& ) = delete; unique_ptr & operator=( const unique_ptr& ) = delete; 実装イメージ 74
  75. // デストラクタ ~unique_ptr() { delete p_; } // アロー演算子(->)のオーバーロード // unique_ptrクラスに対する->の操作を // 内部で管理してるポインタへの操作のように見せる T * operator->() { return p_; } }; 実装イメージ(続き) 75
  76. スマートポインタを使うと ✤ deleteを意識する必要がない ✤ 例外発生時も安全 ✤ メモリを返しても安全 76
  77. void foo() { // 生のポインタを受け取って中で管理するクラス std::unique_ptr<AwesomeClass> p( new AwesomeClass ); // 通常のポインタのように使用できる p->MemberFunction(); p->member_variable_; } deleteを意識する必要がない 77
  78. void foo() { A* p = new A; // この関数内で例外が発生すると function_may_throw( p ); // ここまで実行パスが到達しないので // メモリリーク発生 delete p; } 例外発生時も安全 78
  79. void foo() { std::unique_ptr<A> p( new A ); // この関数内で例外が発生しても function_may_throw( p ); // foo関数を抜ける際に変数`p`の // デストラクタが安全にメモリを解放する } 例外発生時も安全 79
  80. void foo() { A* p = new A; try { function_may_throw(); // 正常系では正しく`p`を解放 delete p; } catch( ... ) { // 異常系でも`p`を解放する必要がある(冗長) delete p; throw; //再スロー(cf, 例外中立) } } try-catchでやろうとすると 80
  81. void foo() { A* p = new A; try { function_may_throw(); } finally { // 擬似コード。C++にはfinallyはない // 正常系でも異常系でも同じく実行される delete p; } } finallyがあれば・・・? 81
  82. void foo() { A* p = new A; try { function_may_throw(); } finally { // 擬似コード。C++にはfinallyはない // 正常系でも異常系でも同じく実行される delete p; } } finallyがあれば・・・? 82 C++にはRAIIや 後述のScopeExitがあるので finallyは必須ではない http://d.hatena.ne.jp/heisseswasser/20130508/1367988794
  83. std::unique_ptr<B> bar() { std::unique_ptr<B> p( new B ); doSomethingWithB( p ); return p; // 関数から安全にポインタを返す } void baz() { std::vector<std::unique_ptr<B>> bs; for( int i = 0; i < 10; ++i ) {     // 安全な形で受け取って、取り扱う bs.push_back( bar() ); } } ポインタを安全に返せる 83
  84. make_uniqueを使おう 84 ✤ make_uniqueとよばれるファクトリ関数を用意 すると、newも意識する必要がなくなる ✤ これは、C++11の標準には入っていない。 C+14から標準入りする予定。ただしC++11環境でも自作 するのは難しくない ✤ http://stackoverflow.com/questions/7038357/make-unique-and-perfect-forwarding ✤ http://herbsutter.com/gotw/_102/
  85. // 動的に作成したオブジェクトを直接 // unique_ptrの形で構築するラッパー関数があれば template<class T> std::unique_ptr<T> make_unique() { return std::unique_ptr<T>( new T ); } // 使う側でnewを意識する必要もなくなる void foo() { std::unique_ptr<AwesomeClass> p = make_unique<AwesomeClass>(); } make_uniqueを使おう 85
  86. // 動的に作成したオブジェクトを直接 // unique_ptrの形で構築するラッパー関数があれば template<class T> std::unique_ptr<T> make_unique() { return std::unique_ptr<T>( new T ); } // 使う側でnewを意識する必要もなくなる void foo() { std::unique_ptr<AwesomeClass> p = make_unique<AwesomeClass>(); } make_uniqueを使おう 86 プログラマがnewもdeleteも意識する必要が なくなった
  87. make_uniqueを使おう 87 ✤ make_uniqueのような、スマートポインタの ファクトリ関数を使用するのには正当な理由があ る ✤ newを使っていると、微妙なケースでメモリリークの原因 となることがある ✤ autoキーワードと組み合わせれば、type量も減らせる
  88. void foo( std::unique_ptr<A> pa, std::unique_ptr<B> pb ) { //... } void bar() { // foo関数呼び出し時の実引数の評価順は不定 foo( std::unique_ptr<A>(new A), std::unique_ptr<B>(new B) ) } newを使うと危険な場合 88
  89. void foo( std::unique_ptr<A> pa, std::unique_ptr<B> pb ) { //... } void bar() { //new Aが評価された直後、unique_ptr<A>の //コンストラクタが評価されるより前に //new Bが評価されるかもしれない foo( std::unique_ptr<A>(new A), std::unique_ptr<B>(new B) ) } newを使うと危険な場合 89
  90. void foo( std::unique_ptr<A> pa, std::unique_ptr<B> pb ) { //... } void bar() { // その場合、new Bが例外を投げたら? // => Aがリークする foo( std::unique_ptr<A>(new A), std::unique_ptr<B>(new B) ) } newを使うと危険な場合 90
  91. // 動的に作成したオブジェクトを直接 // unique_ptrの形で構築するラッパー関数があれば template<class T> std::unique_ptr<T> make_unique() { return std::unique_ptr<T>( new T ); } // make_uniqueの返り値の型はunique_ptrだと // 分かっているのでC++11の型推論キーワードが使える void foo() { auto p = make_unique<AwesomeClass>(); } autoキーワードと組み合わせる 91
  92. void readFileAndOutput() { const char* file_name = "input.txt"; // ファイルからとあるデータの配列を読み込む std::unique_ptr<FileData> file_data = loadFileData( file_name ); for( int i = 0; i < file_data->elements_; i++ ) { outputData( file_data->[i] ); } // 使い終わったら・・・ // => 何もする必要がない! } 最初の例がどうなるか 92
  93. // データを保持する構造体 struct FileData { FileData() : num_data_(0) {} // データ数 int num_data_; // データ列 std::unique_ptr<DataElement[]> elements_; }; 最初の例がどうなるか 93
  94. // ファイルからデータの配列を読み込んで返す std::unique_ptr<FileData> loadFileData( const char* file_name ) { // FileData型のオブジェクトを確保する auto file_data = std::make_unique<FileData>(); // ファイルの内容をfile_dataに読み込む parseFile( file_name, file_data.get() ); // 読み込んだデータを返す return file_data; } 最初の例がどうなるか 94
  95. struct FCloser // デストラクト時に呼ばれる処理 { void operator()( FILE* file ) { fclose(file); } }; // ファイルをオープンして、各行のデータを読み込む void parseFile(const char* file_name, FileData*file_data) { std::unique_ptr<FILE, FCloser> file( fopen( file_name, "r" ) ); fscanf( file.get(), "%d", &file_data->length_ ); 最初の例がどうなるか 95
  96. // 行数分のメモリ領域を確保 file_data->elements_ = std::make_unique<DataElement[]>( file_data->length_ ); // すべての行のデータを読み込み parseElements( file.get(), file_data->length_, file_data->elements_.get() ); } // クリーンアップ用の関数はいらなくなる 最初の例がどうなるか 96
  97. RAIIでメモリリークを制する 97 ✤ スマートポインタはメモリ管理にRAIIという手法 を使用したクラス ✤ スマートポインタを使用すると、メモリリークが 発生しないプログラムが書ける ✤ スライドでは紹介していないが、2重解放やダングリング ポインタも発生しなくなる
  98. モテカワリソース管理術解説 98 ✤ メモリリークはなぜ発生するのか ✤ RAIIによるリソース管理 ✤ RAIIでメモリリークを制する ✤ 他の言語のリソース管理と比較
  99. 他の言語のリソース管理 ✤ 専用のスコープを用意する ✤ スコープを抜ける時に処理をする 99
  100. 他の言語のリソース管理 ✤ 専用のスコープを用意する ✤ スコープを抜ける時に処理をする 100
  101. 専用のスコープを用意する ✤ C# : using構文 ✤ Java : try-with-resource文 ✤ Python : withステートメント ✤ Ruby : ブロック 101
  102. public static void Function ( string strFilename ) { using ( StreamWriter stream = new StreamWriter ( strFilename ) ) { stream.WriteLine("Loan Pattern!"); } } C# 102
  103. File::open( "output.txt", "w" ) { |f| # オープンされたファイルオブジェクトを # 使ってごにょごにょ f.puts "Block!" } # ブロックを抜けると自動でクローズされる Ruby 103
  104. Loan Pattern 104 ✤ これらの言語のリソース管理の仕組みは、 ある関数や文のスコープ内でリソースを確保する ようになっている ✤ C++のような、リソース管理がクラスのオブジェクトに 紐づく仕組みとは異なる点 ✤ ScalaではLoan Patternと呼ばれる ✤ とあるスコープの中でだけリソースを拝借する ✤ Loan Patternも先に挙げたRAIIの3つの利点 (カプセル化、例外安全性、局所性)を持つ
  105. RAII vs. Loan Pattern ✤ RAIIはリソースの管理に専用の構文が必要ない ✤ RAIIではリソースがオブジェクトに紐づいている ✤ リソースを持ち運べる ✤ スコープを超えてリソースを保持できる ✤ Loan Patternでは実現できないRAIIの利点 105
  106. // なにか条件によって初期化状態が // ことなるリソースを作る関数 MyResource foo( Condition cond, Data data ) { MyResource res( getResourceHandle() ); if( cond ) { res.setStateWithData(data); else { res.setAnotherState(); } return res; } リソースの受け渡し 106
  107. C++でLoan Pattern ✤ Loan PatternはC++でも実装可能 ✤ http://dev.activebasic.com/egtra/2014/03/24/654/ ✤ http://carefulcode.blogspot.jp/2013/05/rifl-vs-raii.html 107
  108. 他の言語のリソース管理 ✤ 専用のスコープを用意する ✤ スコープを抜ける時に処理をする 108
  109. スコープを抜ける時に処理をする 109 ✤ ScopeExitと呼ばれたりする ✤ D言語 ✤ scope(exit), scope(failure)など ✤ RAIIも使える ✤ Go言語 ✤ defer句(関数スコープのみ)
  110. void abc() { Mutex m = new Mutex; lock(m); // mutexをロック // スコープ終了時にアンロック scope(exit) unlock(m); foo(); // 処理を行う } scope(exit) (D言語) 110 参考: http://www.kmonos.net/alang/d/exception-safe.html RAIIのようなリソース管理用の専用の クラスを作る必要がない => 簡単
  111. void abc() { Mutex m = new Mutex; lock(m); // mutexをロック // スコープ終了時にアンロック scope(exit) unlock(m); foo(); // 処理を行う } scope(exit) (D言語) 111 参考: http://www.kmonos.net/alang/d/exception-safe.html エラーが発生したときにだけ呼び出される scope(failure)もあったり
  112. C++でもScope Exit 112 ✤ Boost.ScopeExitのように、C++でもScopeExit を実現可能 ✤ ただし言語機能ではなくライブラリ実装なので、D言語よ りは冗長さがある ✤ https://sites.google.com/site/boostjp/tips/scope_guard ✤ www.boost.org/doc/libs/release/libs/scope_exit/
  113. まとめ 113 ✤ C++ではクラスの仕組みの中に、メモリ管理を 隠 できる ✤ クラスの仕組みの中でリソースを管理する手法を RAIIと呼ぶ ✤ RAIIを使ったスマートポインタというクラスで メモリリークを回避できる
  114. 参考文献やサイトなど ✤ Effective C++ 第3版 ✤ Exceptional C++ ✤ C++ ポケットリファレンス ✤ Working Draft, Standard for Programming Language C++ N3337 ✤ http://herbsutter.com/gotw/_102/ ✤ https://sites.google.com/site/boostjp/tips/scope_guard ✤ www.boost.org/doc/libs/release/libs/scope_exit/ ✤ http://d.hatena.ne.jp/heisseswasser/20130508/1367988794 ✤ http://dev.activebasic.com/egtra/2014/03/24/654/ ✤ http://www.ne.jp/asahi/hishidama/home/tech/scala/sample/using.html ✤ http://carefulcode.blogspot.jp/2013/05/rifl-vs-raii.html ✤ http://www.kmonos.net/alang/d/exception-safe.html 114
Publicidad