More Related Content Similar to イマドキC++erのモテカワリソース管理術 (20) イマドキC++erのモテカワリソース管理術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
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
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
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
40. public static void foo()
{
// 動的に確保したメモリ領域
int[] numbers = new int[10];
numbers[0] = 100;
numbers[5] = 200;
numbers[9] = 1000;
// 解放の処理は書かなくていい
// プログラム上でメモリ領域が不要になったら
// GCが適当なタイミングでその領域を解放する。
}
GC付き言語の例(Java)
40
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
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
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
クラスがメモリを管理しているのでプログラマが
メモリ管理を意識する必要がない
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
61. std::mutex g_mutex;
void thread_proc( AwesomeSharedData* d )
{
std::lock_guard<std::mutex> lock( g_mutex );
ModifySharedData( d );
}
Mutexの例
61
ロック状態というリソースを取得したい
=> そのためのオブジェクトを作って初期化する
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()が呼ばれるのを保証できる
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
75. // デストラクタ
~unique_ptr()
{
delete p_;
}
// アロー演算子(->)のオーバーロード
// unique_ptrクラスに対する->の操作を
// 内部で管理してるポインタへの操作のように見せる
T * operator->()
{
return p_;
}
};
実装イメージ(続き)
75
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
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
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
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
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
105. RAII vs. Loan Pattern
✤ RAIIはリソースの管理に専用の構文が必要ない
✤ RAIIではリソースがオブジェクトに紐づいている
✤ リソースを持ち運べる
✤ スコープを超えてリソースを保持できる
✤ Loan Patternでは実現できないRAIIの利点
105
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
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)もあったり
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