JAVA や C# などの新しい言語は、ガーベジコレクタと呼ばれるメモリ自動管理機構を言語の仕様として持っている。 それに対して C/C++ はメモリを管理するコードを手動で書かなければならない。 これを簡単にコードとして書くならばすなわち、
以上のように、生成(new)したオブジェクトは必ず自分で破棄(delete)せよということである。 スマートポインタとは、この生成・破棄のパターンをカプセル化によって一般化し、 生成したオブジェクトを自動的に破棄する機構を提供するものである。 といってもこれはそんなに特別なものではなく、あくまでC/C++の言語の上にのった小さなプログラムに他ならない。 スマートポインタの正しい使い方を覚えるとプログラムがスマートに書けるようになる。 ここではスマートポインタを使った例をいくつか紹介する。
まずはスマートポインタが使えるようにコンピュータをセットアップしなければならない。 スマートポインタは自分で作ることもできるが、Boost C++ Libraries というライブラリにその実装が含まれており、 本ページでは主にこのスマートポインタを用いる。 以下のサイトを訪れて Boost C++ Libraries をコンピュータにインストールしよう。
このページは英語で書かれているが、ページ左のDownloadをクリックして先に進んで行けば良い。 基本的にスマートポインタはテンプレートとして書かれているのでコードをコンパイルするという必要は無く、 インクルードディレクトリとしてライブラリのルートディレクトリにパスを通せばセットアップは完了である。
スマートポインタを使わないプログラムと、 使ったプログラムの比較を行おう。 まず最初に、スマートポインタを使わない一番簡単なプログラムを以下に示す。
Class1は、生成されたときに呼び出されるコンストラクタと破棄されたときに呼び出されるデストラクタで メッセージを出力する簡単なプログラムである。 また、Class1はメソッドを一つ持っていて、それを呼び出すことでもメッセージを出力する。 Class1は属性として名前を持っており、何という名前のオブジェクトがメッセージを出力しているのかがわかるようになっている。
このサンプルのメイン関数は実に簡単で、Class1のオブジェクトaを構築し、 methodを呼び出し、最後に破棄するだけのものである。 これはまさにオブジェクトの生成と破棄の最も簡単なサイクルそのものであり、 以上のプログラムをコンパイルして実行すると以下のような結果が得られる。
以上のプログラムをBoost C++ Librariesの boost::shared_ptrというスマートポインタを使って書くと以下のようになる。
boost::shared_ptrを使うためには、まずboost/shared_ptr.hppをインクルードする。 次に、shared_ptrはテンプレートなのでClass1をテンプレートに当てはめ、 Class1専用のスマートポインタとしてClass1Ptrという型を定義する。
このサンプルのメイン関数は実に簡単で、 Class1のオブジェクトaを構築し、そのメソッドmethodを呼び出している。 前のサンプルと違いオブジェクトaを破棄する記述が無いが、このプログラムを実行すると 前のサンプルと同じ実行結果が得られる。 なぜそうなるかというと、Class1Ptrクラスがオブジェクトaの破棄を自動的にしてくれるからである。 具体的には、Class1Ptrクラスのデストラクタでオブジェクトaは破棄されている。 簡単に言うとClass1Ptr変数のスコープが、 生成したオブジェクトの生存区間を決定するようにプログラムを書くことができるようになったということだ。 また、Class1Ptrクラスはアロー演算子(->)を適切にオーバーライドしており、 普通のポインタのメソッドを呼び出すときと同じようにしてメソッドを呼び出すことができる。
ついでに以上のプログラムのメイン関数をちょっとだけ書き換えて、 a,bという二つのオブジェクトを生成するように以下のように書き換えて見よう。
すると実行結果は以下のようになる。
ここで重要なのは変数を宣言した順番と、その破棄の順番がちょうど逆になるということである。 これでスマートポインタの一番簡単な使い方の説明を終える。
スマートポインタの使い方に関してこれ以上詳しく説明はしないが、 さまざまな局面におけるスマートポインタの使い方を示すサンプルプログラムを以下にいくつか示す。 いずれもスマートポインタを使った一番簡単なプログラムを、 ある問題に焦点を当てて書き直した非常に簡単なものなので必要に応じてちょっと見て動かして見ると 何かわかるかもしれない。
オブジェクトを破棄するdeleteは、単体のオブジェクトの場合と配列の場合とでは異なる。 そのため配列のポインタを扱う場合にはboost::shared_ptrの代わりにboost::shared_arrayを用いなければならない。
スマートポインタも普通のポインタと同じように、もちろん関数に渡すことができる。 これはそれだけの簡単な例である。
ポインタ変数が、途中で別のポインタを指すようになるという例。 この場合、ポインタ変数は新しいオブジェクトのポインタを代入する前に 必要ならば古いオブジェクトを事前に破棄しなければならない。 スマートポインタは代入時に自動的に古いオブジェクトを破棄してくれる。 必要ならばとは、そのオブジェクトを参照しているポインタ変数が他にもある場合は、 そのオブジェクトを破棄してはならないからである。 boost::shared_ptrというスマートポインタは、参照カウンタ型スマートポインタと呼ばれるもので、 自分が保持するポインタがどれだけ使われているかという数を自動的に数えており、 それに従ってポインタを破棄するべきかどうかを判断している。 すなわち参照カウンタが0になったら、そのオブジェクトは破棄して良いということだ。
上のサンプルでは、オブジェクトが常にただ一つのスマートポインタによってしか参照されておらず、 参照カウンタの効果がいまいち発揮されていないが、これはそれを発揮している簡単な例である。
スマートポインタも普通のポインタと同じように、もちろんポリモフィズムを実現することができる。 これはその簡単な例である。
生成と破棄は対で現れるので、関数の入口と出口でこれを制御すれば良いという考え方がある。 ただし、関数の入口は常に一つだが出口は常に一つとは限らない。 そんな場合、全ての出口で適切な破棄の記述を書かなくてはいけない。 スマートポインタを使うと、そのわずらわしさから解放されてスマートな記述ができるという例である。
スマートポインタも万能ではない。スマートポインタがうまく働かずオブジェクトが正常に破棄されない場合がある。 それはポインタの参照がサイクリック(aがbに、bがcに、cがaにとグルグル周ってしまう関係)になってしまったときである。 なぜならお互いの参照カウンタが常に0にならないからだ。これはその簡単な例である。 スマートポインタは簡単で非常に効果的だが万能ではないことを知っておこう。
スマートポインターの存在に出会った最初の本である。 スマートポインターのみならず C++ における数多くの慣習や技能を身につけることができる。 2010/10/22 辞典での Amazon のカスタマーレビューを見たところによると日本語訳が酷評されているが、 ある程度の能力を事前に持っていればサンプルとして掲示されているソースコードから、逆に文章の誤りを正して読み直すことができるだろうと思う。 本書は、既に古く新改訂版が出ているようなのでそちらの購入をお勧めする。 残念ながら新改訂版を読んではいないが、スマートポインターの記載がなくなっているとは思えない。 欲しいと思う人は一度手にとって見てから買うと良いだろう。
More Effective C++ は、前作に Effective C++ という本があり、その続編である。 したがって、Effective C++ も参考にすることでスマートポインターのみならず多くの知見を得ることができるだろう。
こちらも新改訂版が出ているようなので、これから購入を検討する場合はそちらの購入をお勧めする。
本ページで紹介している Boost C++ Libraries の解説書である。 Boost C++ Libraries は、多くのライブラリの集合体で合うr。 スマートポインタを実装する boost::shared_ptr だけではなく、他にも多くの能力を秘めたライブラリである。 ライブラリを概観するのに、また、API の即時参照に活用できる本である。
こちらも改訂版が出ているようなので、これから購入を検討する場合はそちらの購入をお勧めする。