RAII解説

この記事は[初心者 C++er Advent Calendar 2015](http://www.adventar.org/calendars/922)の21日目の記事です．
昨日は[shunonymous](https://twitter.com/shunonymous)さんの「[ C++でプログラムの動作を一定時間止める](http://shunonymous.github.io/shunomemo/2015/12/20/cxxsleeep/)」でした．

まるで初心者向けの記事を書けないかのような言われようですが[^0]，氏の期待通り初心者向けの記事を書いたつもりです．
初心者の皆さんがこの記事を読んで初心者から一歩脱してくだされば幸いです．
また，マサカリは大歓迎なのでバンバン投げてください．

[^0]: クソザコなどと自称してconstexpr-Lambdaライブラリを作った話を発表したりするとこういったあらぬ誤解を受けるので気をつけましょう(要するに自業自得です)

---

#**あじぇんだ**
C++において多用されるRAIIという技法の考え方とメリット・デメリットについて，同様の目的で用いられることのあるGCとの対比も交えつつ紹介していきます．
**ちなみに私はGCとかJavaとかよく知らない初心者なので，大嘘を書く可能性があります．もし誤った記述を見かけた際には是非コメントを下さい．**
初心者向けです．
考え方自体はC++98でさえも通用しますが，コードはC++11で書いていきます．

#**前提知識 : スコープ**
さて，RAIIについて語る前に前提知識として変数のスコープについての知識が必要ですので，簡単に触れておきたいと思います．
ここでいうスコープとは，変数の生存範囲のことを指します．

```cpp
int main(){
  std::cout << "ここはまだなにもない" << std::endl;
  {
    int a = 0;
    std::cout << "ここで変数aが登場" << std::endl;
    std::cout << a << std::endl;
  }//!Here!
  std::cout << "もうaはいない" << std::endl;
  //std::cout << a << std::endl; //Error!!
}
```

上のコードで，7行目の`//!Here!`の部分に`}`がありますが，このようにC++の局所変数[^1](この場合は`int a`です)は`{`と`}`の間(ブロックと呼びます)で定義されたとき，`}`を越えた際に破棄されます．
この定義されてから`}`までが変数の生存範囲・スコープであり，該当する変数を使うことが出来るのはこの範囲内のみです．
これはブロックの手前に`while(cond)`[^while]や`if(cond)`などの制御構文が来ても同様です．
また関数の中身を記述する`{`と`}`の間でもスコープは存在しますので，関数内で定義された局所変数は関数の呼び出しが終了した(`return`が呼ばれた，例外送出で関数を脱した，などの)ときに破棄されます[^2]．

[^1]: ローカル変数とも．関数内で`static`などを用いずに宣言された変数だと考えてください．
[^2]: 関数の外から関数内の変数にはアクセス出来ないのも関数内にスコープが存在するからです．
[^while]: `while(cond){...}`の場合は，`cond`を評価→trueだったら`{...}`を実行→`cond`を評価…となるので，各ループ間(例えば，1回目の`{...}`と2回目の`{...}`同士)で局所変数は共有できません．ループ毎にブロック内で定義された局所変数は破棄と生成が繰り返されます．

#**RAIIとは**
RAIIとは**R**esource **A**cquisition **I**s **I**nitializationの略で，リソースの確保(Acquisition)と解放を変数の初期化(Initialization)と破棄に紐付けるという考え方を指す言葉です．

##**例 : `std::unique_ptr`**
と言われて，何を言っているのかさっぱり分からない，という方も多いと思いますので，1つ例を挙げましょう．
[13日目にてやましろさんが紹介なさっている](http://tokkaka.hatenablog.com/entry/2015/12/13/164017)スマートポインタです．

```cpp
#include<memory>

int main(){
  std::cout << "ここはまだなにもない" << std::endl;
  {
    std::unique_ptr<int> ptr = new int(3);
    std::cout << "ここでメモリを確保かつ変数を初期化" << std::endl;
    std::cout << *ptr << std::endl;
  }//!Here!
  std::cout << "ptrはもう存在しない" << std::endl;
}
```

スコープの例と殆ど何も変わりませんね．
6行目では`new`を用いてメモリという**リソースの確保**を行い，同時に`std::unique_ptr<int> ptr`の**初期化**を行っています．
そして，9行目の`//!Here!`の`}`を越える際にブロック内の`ptr`が**破棄**されることになります．
しかし，`ptr`はスマートポインタですので，ここで自動的に`delete ptr.get()`，即ちメモリという**リソースの解放**が実行されます．
と，このようにリソースの確保と変数の初期化，リソースの解放と変数の破棄を紐付けるのがRAIIです．

ちなみに，unique_ptrを使わずに書くとこんな感じ．

```cpp
int main(){
  std::cout << "ここはまだなにもない" << std::endl;
  {
    int* ptr = new int(3);
    std::cout << "ここでメモリを確保かつ変数を初期化" << std::endl;
    std::cout << *ptr << std::endl;
    delete ptr;//メモリを解放
  }//ptrを破棄
  std::cout << "ptrはもう存在しない" << std::endl;
}
```

このように，メモリの解放処理を手で書く必要があります．
忘れたらメモリリークです．面倒くさいですね．


##**何故RAIIが重要なのか**

0. メリットがたくさんあって
1. C++との親和性が高く
2. 結果標準ライブラリでも多用されている

からです．綺麗なC++コードを書くためにはRAIIの理解が必須だと思います．
どちらにせよ，標準ライブラリで多用されているので学ぶ価値はあると思います．


## **RAIIのメリット**

###**GCとの比較**
一般にメモリを自動で解放する，と聞くとGC(Garbage Collection)を思い浮かべる方も多いと思いますが，RAIIはGCと比較して優れた点がいくつかあります．

####**解放タイミングが明確**
GCは不要になったオブジェクト(とそのメモリ領域)を自動で判断して解放しますが，これがいつ実行されるかはGCのみが知っていることで，我々が知ることは出来ません．
また，不要になった瞬間に解放したい，とかこのタイミングで解放したい，といった小回りが効きません．
一方，RAIIのリソース解放タイミングは「スコープを抜けたとき」と明確です．コードのどこで解放されるのかが我々でも分かります．
またRAIIでは手で解放処理を明記しないというだけでしかなく，スコープの終了位置によってある程度解放処理のタイミングをこちらで決めることが出来ます．

####**どんなリソースでも同じ方法で正しく解放出来る**
GCはメモリの解放は出来るかもしれませんが，より広範なリソースへの対応に関して，RAIIには一歩及ばないと言えるでしょう．
Javaでループ内で生成した同一ファイルへの`FileOutputStream`の`close()`を忘れると例外を吐きます．
Javaの`FileOutputStream`はGCで回収されたときに自動で`close()`が呼ばれる作りになっています[^3]が，GCで回収された直後ではなく2回ほどたらい回しされた末に呼ばれるため解放が間に合わず，まだそのファイルを開いているハンドルが残っているうちに再度開こうとしてしまうことが原因です[^4]．

また，件のJavaの自動解放は解放順序が規定されていないという大変問題な作りになっています．
つまり，リソースAに依存するリソースB，という構図でリソースBの解放が優先されない可能性があるのです．
C++のスコープは，定義した順番とは逆順に変数を破棄していきます．
従って，リソースAを生成し，次にAを使ってリソースBを作った場合，RAIIを使えば確実にリソースBが先に解放されます．

挙げ句の果てに，件の自動解放はプログラム終了時点で必ず解放されるという保証は無いとのこと[^5]．
もちろんRAIIはスコープを脱したときに必ず解放します．それこそ，プログラムの終了時には静的変数[^6]だろうと大域変数[^7]だろうと生成とは逆順に破棄します[^8]ので，リソースを確実に解放します．[^9]

このように開放タイミングが不明確なリソース管理は，リソースリークを助長します．
「今解放されなくてもいずれ解放されればいいじゃん」と思うかもしれませんが，昨今のモバイル環境全盛時代，バッテリーの消費を削減するためにもバッテリーセーバーの抑制は必要最低限の時間かつ出来る限り自動で行うべきです．
バッテリーセーバーの抑制を悠長にやったり，うっかりセーバーの起動を忘れたりすると電力消費の増大を招きます[^13]．
無駄にバッテリーの消費が激しいアプリは良くないですよね？

そして何よりRAIIが素晴らしいのは，リソースの確保と解放を統一的に扱える点です．
RAIIを使ったとき，オブジェクトを構築し，スコープによってそれを破棄する，ただそれだけを考えればよいのです．

[^3]: らしいです．finalizeメソッドなる機構で実現されているとか．
[^4]: これも解放タイミングが不定なことが問題の根幹にあります．
[^5]: プログラム終了時にGCが走らないため，オブジェクトに紐付いたリソースが解放されない…ということらしい．一般的なOSであれば，アプリケーション終了の際にメモリ自体は開放される．
[^6]: `static`が付いてる変数
[^7]: 関数外・クラス外で宣言されてる変数
[^8]: ただし，翻訳単位を跨いだ静的変数の初期化順序は未定義なのでうまいことやらないとおかしくなります．
[^9]: 一応補足しておくと，Javaも2011年の規格であるJava 7でtry-with-resources構文なるものに対応したことで，RAIIのように`try`ブロックを抜けたところで自動で`close()`をすることが出来るようになっているみたいです．ただ，複数のリソースを同時に定義しない場合(具体的には`java.sql.PreparedStatement`と`java.sql.ResultSet`とか)はオブジェクトの定義毎にネストが深くなっていく(`PreparedStatement.setHoge`を呼び出そうとすると`ResultSet`の定義が`PreparedStatement`の定義と同時には行えませんので，別の`try`ブロックに記述する必要があります)ので，残念ながらC++におけるRAIIほど読みやすくはならないかなぁ，というのが個人的な見解です．ただし，close処理の際に発生した例外を(`try`ブロック・`catch`ブロックどちらの例外も含めて)正しく処理出来る点は好感が持てます．
[^13]: 電力バグ(ebugs)などと呼ばれています．

###**それ以外のメリット : 例外安全**
さて，冒頭のスコープの説明に以下の様な記述をしました．
> 関数内で定義された局所変数は関数の呼び出しが終了した(`return`が呼ばれた，**例外送出で関数を脱した**，などの)ときに破棄されます

つまり，

```cpp
void f(){
  int* ptr = new int(42);
  call_throwable_function(); //この関数内で例外が送出されたとすると自動的に例外はこの関数の外に再送出される
  delete ptr;  //結果このdeleteは実行されない，リソースリークが発生
}
```

のような問題も，

```cpp
void f(){
  std::unique_ptr<int> ptr = new int(42);
  call_throwable_function(); //この関数内で例外が送出されたとすると自動的に例外はこの関数の外に再送出される
}//例外送出だろうとなんだろうとスコープを抜ければptrは破棄されるのでリソースリークは発生しない
```

のようにRAIIを使えば起こりえないということ．これにより，例外安全[^11]なコードの記述が(使わない場合と比べれば)いともたやすく行えるようになったのです．
ちなみに，これをRAII無しで防ぐためには以下のようになります．

```cpp
#include<stdexcept>
void f(){
  int* ptr = new int(42);
  try{
    call_throwable_function(); //この関数内で例外が送出されたとすると
  }catch(...){
      delete ptr; //ここでptrが指す先のメモリ領域を開放してから
      throw; //例外再送出
  }
  delete ptr;  //例外が発生しなかった際にはこちらのdeleteが実行される
}
```

当然リソース確保/解放が増えてくればコードはより複雑化していきます．RAIIは簡潔なコードと確実な動作を両立するのです．

[^11]: 例外が発生した際にプログラムの動作に影響を及ぼさないこと…みたいな概念．例外が発生しても変数の値が例外発生前と変わらないとか，例外が発生しても自動でリカバリして動作を続けるとか．ここでは，例外によってリソースリークが生じないこと．

###**RAIIのデメリット**
デメリットというよりは**RAIIが不向きな場合**，と言ったほうが正しいかもしれません．
RAIIはデストラクタを用いてリソース解放を自動で行う性質上，解放に失敗してもそれを知る術が非常に限定的になります[^12]．
特にその失敗が**例外送出**という形で通知される場合，やっかいな問題となります．

例えばRAIIによって管理されたファイルハンドルのcloseに失敗し，例外を送出したとしましょう．これが発生したのはデストラクタです．
**C++において，デストラクタで例外を投げるのは基本的にご法度となっています**．
理由ですが，まずそのデストラクタの処理が例外を投げたところから先全てすっ飛ばされるため，リソースリークを起こしかねない[^17]こと．
次に，例外送出中のスコープ脱出によって呼ばれたデストラクタ内でさらに例外を送出した場合[^14]，プログラムは終了処理もすっ飛ばして即落ちします．当然リソースリークです．
前者はどうしようもないですし，後者についてもこれを防ぐためにはデストラクタを以下のようにするしかありません．

```cpp
class class_name{
 public:
  ~class_name(){
    try{
      close(); //例外を発生しうる解放処理
    }catch(...){
      //なにかするとさらなる例外を生む可能性があるのでなにもしない
    }
  }
};
```

これによってリソースリーク自体は防げますが，万一`close`に失敗したとしてもその情報を得る手段は実質的に皆無[^12]ですから，**解放処理の失敗をトラップしたい場合にはどうしようもなく使いにくい**ということが分かるかと思います．
こういった場合には，例えば標準ライブラリの`std::fstream`などには`close()`メンバ関数があるので事前に呼び出すことで例外が発生した際に処理を行うことが可能です．

[^12]: 例えば戻り値チェックやデストラクタ内部でのtry-catchを使うことで失敗を検出し，それを外部の変数に突っ込むことで後から成否を確認すること自体は可能です．とても良い設計とは思えませんが…
[^14]: 例外発生中にさらに例外を発生させることをdouble faultなどと言い，これに陥った場合C++では`std::terminate`関数を呼ぶことになっています．こいつはプログラムのプロセスを後処理など考えずにその場で殺す関数です．
[^17]: 例えば2つのリソースを扱っていた場合，先に解放するリソースの解放時に例外が発生すると後で解放するつもりだったリソースは解放されないままとなります(例外安全のサンプルコードを思い出してください)

###**RAIIのメリットまとめ**
0. どんなリソースでも対応するオブジェクトを作るだけ
1. 解放処理を手で書かなくても勝手に解放してくれる
2. 解放のタイミングが明確
3. 例外送出時もちゃんと対応

安全なコードを簡単に書ける，これほど素晴らしいことはありません！

##**RAIIは何故C++との親和性が高いのか**
RAIIを実現するためには，「オブジェクトの破棄のタイミングで呼び出される処理」というものが必要になります．そこでリソースの解放処理をするからです．
C++には，デストラクタというまさしくそれそのものな存在があるため，RAIIの実現が容易なのです．

また，個人的にはC++の変数の扱いが「値のセマンティクス[^15]」であることは非常に重要な点だと考えています．
値のセマンティクスであるということは，オブジェクトとリソースを完全に1対1対応させることが可能だということです．
オブジェクトをコピーしたらリソースもコピーする，といったようなクラス設計が考えられます[^18]．
もしこれが「参照のセマンティクス[^16]」であった場合，結局「リソースオブジェクトのハンドル」でしかないため，オブジェクトとリソースの対応づけ，という考え方があまり浸透しなかったのではないかなぁ，と思います[^19]．

[^15]: 変数の概念が，文字列型`String`に対して，`String a, b = "fuga"; a = b; b = "hoge";`とした時に`a == "fuga"`が成り立つようなものであること．つまり，`a`と`b`は別個の変数であり，それぞれがメモリを持ち，値の代入はコピーである，という扱い．
[^16]: 変数の概念が，文字列型`String`に対して，`String a, b = "fuga"; a = b; b = "hoge";`とした時に`a == "hoge"`が成り立つようなものであること．つまり，`a`と`b`は特定のオブジェクトに対する参照でしかなく，値の代入は参照先の切り替えである，という扱い．Javaの`Object`はこっち．C++だとポインタや`std::shared_ptr`などはこちらに近い．
[^18]: 後述しますが，`std::string`やSTLのコンテナはこうなっています．
[^19]: `std::shared_ptr`なんかは見事にこの考え方に反した存在ですが，あれはあくまでポインタのラッパーなので…(言い訳)

##**C++標準ライブラリにおけるRAII**
###**スマートポインタ**
`<memory>`ヘッダに定義されている，`std::shared_ptr`や`std::unique_ptr`など．
従来は`malloc`/`free`や`new`/`delete`といった確保と解放をセットで操作しなければならなかった動的メモリ確保ですが，スマートポインタの登場によって解放が自動化されます．
###**`std::string`**
そもそもC言語における文字列とは「ヌル文字終端のメモリ上に連続した文字型の値の列」であり，ものすごく大雑把に言えば`char`の配列(とか)です．
実際には文字列長が可変であることを考えると，固定長配列ではなく実行時に動的に長さを変更できる作りにする必要があります．そう，メモリの動的確保です．
というわけで，`<string>`ヘッダに定義されている`std::string`は`find`や`append`，`empty`などの便利なメンバ関数ももちろんですが，そもそも可変長の文字列のためのメモリ管理を完全に手放しで行えるクラスなのです．
###**STLコンテナ**
`std::vector`などのSTLコンテナは要素を格納するためのメモリを動的に確保し，またコンテナ自体を破棄する際にコンテナの要素となっているオブジェクトをそれぞれ破棄，最後にメモリを解放する，という作りになっています．

また，`std::string`や`STLコンテナ`はみな，C++の値のセマンティクスに沿った挙動をします．つまり，それぞれのオブジェクトのコピーを取ると必要なだけのメモリが別に確保され，そこに値がコピーされます．
リソースとオブジェクトの1対1の対応が取れていて，個人的には非常に分かりやすいと思います．

###**`std::fstream`**
`std::fstream`は仮にファイルをオープンしたままの場合，自動で`close`メンバ関数を呼ぶためファイルの閉じ忘れの心配がありません．ファイルそのもの化のように扱うことが出来ます．
ただし，先述の通り`close`は例外を発生する可能性があるので，正しくハンドリングしたい場合には手動でcloseをしてその結果を確かめる必要があります．

###**`std::lock_guard`**
初心者向きではない気もしますが，`std::lock_guard`というクラスもRAIIを用いています[^10]．
これは，並行処理の際に複数のスレッドが同時に同じメモリ領域にアクセスしないよう[^20]にロックという処理を行い単一のスレッドのみがアクセスできるようにするのですが，これもまた`lock`をしたら`unlock`をせねばならないのです．RAIIを使うことで，`unlock`忘れを防止できます．

[^10]: Lockをリソースと考えれば，Scoped Locking PatternもRAIIの一種です．
[^20]: タイミングによって結果がおかしくなる可能性があるためです．よく言われる例え話だと，AさんとBさんが同じ銀行口座を使っていて，Aさんが入金，Bさんが引き落とし，を同時に行った結果Aさんの入金分の額がどこかにふっとんだ(Bさんが引き落とした結果の額が口座に書き込まれた)…といったようなことが起こりえます．


#**まとめ**
多くの場合デメリットが無いし，便利なのでみんなもRAIIとRAIIを使ったライブラリ(標準ライブラリ含む)を使おう．

---

というわけで，明日は22日目，[yumechi](https://twitter.com/yumechi0525)さんです．

---