クラス構築編リンクリスト方式なスマートポインタかなり前になりますが、マルペケではクラス構築編「スマートポインタテンプレートクラス」及び「改改・スマートポインタテンプレートクラス(交換サポート)」にてスマートポインタの概念と実装について説明してきました。概要を説明すると、スマートポインタというのはnewする事によって動的に確保したオブジェクトの自動解放をサポートしてくれます。これによりスマートポインタにnewしたオブジェクトを包む限りメモリリークは起きません(循環参照した場合は除きます)。詳しくは上記リンク先の記事を御覧ください。スマートポインタの実装方法としてよく知られていてマルペケ謹製スマートポインタ(Dix::sp<T>)でも採用しているのが「参照カウンタ方式」によるスマートポインタです。これは同じnewしたオブジェクトポインタを参照している人の数を数える(参照数を共有する)事により、最後の一人がいなくなる時にだけdeleteする事で確実にオブジェクトを解放できる方法です。この方法は概念的にも分かりやすく、基本機能だけにしぼるなら実装もそれほど難しくありません。実はこの方法、一つ大きな問題を抱えています。それは「参照カウンタの共有に伴なうnewの発生」です。空のスマートポインタを作る時、内部では共有カウンタの保存先となる数値型(int型等)をヒープから取る、すなわちnewが発生します。オリジナルなメモリアロケータ(メモリ管理者)を作成しているなら別ですが、標準のnewの動作速度は速くありません。それは要求されたサイズのメモリを確保するための検索や管理領域の確保に時間がかかるためです。newの回数が少ない分にはあまり影響しませんが、これが毎フレーム何十万回ともなりますと、その速度は深刻なものになります。「毎フレーム何十万回にもなるの?」と思われるかもしれません。これは案外あっさりとなってしまいます。関数の引数にスマートポインタを渡すとしましょう。この時、値渡しをすると一度空のスマートポインタがローカルメモリに作られ、次に代入が行われます。newが1回発生するわけです。別の例で、クラスのメンバにスマートポインタが1つあるとすると、そのクラスのオブジェクトを作るだけでnewが発生します。関数の戻り値にスマートポインタを渡しても発生します。あらゆる所で参照カウンタ用のnewが発生する可能性があるわけです。newは遅い、とにかく遅い。そのため、可能な限りnewの発生回数を抑える必要があります。そこで「リンクリスト方式によるスマートポインタ」というのがあります。これは内部でnewが発生しません!この章ではそんなスマートポインタについて掘り下げてみます。リンクリスト方式なスマートポインタの理屈スマートポインタはnewされたオブジェクトを包みます。これは参照カウンタ方式でも、これから掘り下げるリンクリスト方式でも変わりません。異なるのは「同じオブジェクトを参照している人を認識する方法」です。先に説明したように、参照カウンタ方式は同じオブジェクトを参照している人の『数』を内部で共有する事で認識していました。リンクリスト方式では『数』ではなくて『お隣さん』として共有を認識します。リンクリスト方式なスマートポインタは、内部に同じスマートポインタの双方向リンクリストを持っています。共有がある場合は一方の手を相手に差し出し、相手からも手を貰う事でがっしりと握手します。これにより共有が実現します:共有が起こる度にこのリストの挿入を行えば、一本の長いリンクリストが出来ていきます。共有から外れる時(=スマートポインタが削除される時)はリンクリストから切り離せば良いだけです。最後の一人かどうかは、自分が消える時に両手に誰もいないかどうかで判定ができます。この方式でどうして内部でnewが発生しないのでしょうか?双方向リンクの『手』の型は「スマートポインタのポインタ」です。これはヒープから取る必要はありません。相手の生ポインタをダイレクトに持ってしまっていいんです。「怖!」と思われるかもしれませんが、スマートポインタの性質がこれを可能にしてくれています。共有が発生した時、少なくともメモリのどこかに相手のスマートポインタの実体が存在します。アドレスが割り振られているわけです。このアドレス先の人と手を結ぶ事で共有が実現します。相手がいなくなる時にこの手は離されます。手がつながっている間は相手の存在が保証されているんです!スマートポインタをヒープに取ろうが小さな関数の中で一時的にスタック上に取ろうが、これは同じことです。相手のアドレスに実体が保証されているのであれば、生ポインタで持っても実質安全です。これにより、内部でnewをして共有先を確保する事がなくなるわけです。副次効果として、リンクリスト方式のスマートポインタのコンストラクタは大変に軽いです。newされたオブジェクトへのポインタの代入以外はリンクリストの両手にNULLを入れるだけです。参照カウンタ方式の場合はここにカウンタ用のnewが発生します。その生成コスト差は雲泥です。リンクリスト方式のスマートポインタを実際に作ってみましょう。双方向リストの挿入・切り離しが出来れば終わりですまず、このスマートポインタに必要なメンバから整理します。スマートポインタはnewされたオブジェクトを包むので、当然ですがそのオブジェクトへのポインタを格納する必要があります。次に、相手のスマートポインタへの双方向リストがあるので、スマートポインタのポインタを2つ持ちます。双方向リンク};概念的には正しいのですが、これ、実はちょっと正解ではありません。言うのも、双方向リンクに「オブジェクトの型」の情報は必要無いからです。お隣にポインタがあるかどうかが分かれば良いだけなんです。newされたオブジェクトへのポインタ};こうする事で、相手と手をつないだり話したりする仕事とオブジェクトの共有を分離できます。インターフェイス別に検討していきましょう。コンストラクタspクラスのコンストラクタは複数存在します。{}};引数に渡されたnewしたオブジェクトへのポインタを内部に包みます。双方向リストはsp_baseクラスのコンストラクタが自動的にNULLに初期化してくれます。src.getPtr();}};joinメソッドはsp_baseクラスが持っていて、引数に渡されたスマートポインタを自分のリストに加えます。this;}};双方向リンクの接続は落ち着いてやれば簡単です。後は相手のオブジェクトポインタを共有すれば同型スマートポインタのコピーコンストラクタ終了です。代入にはもう一つアップキャスト代入があります。src.getPtr();}};もしアップキャスト不可能な代入がソース内にあると、このコンストラクタでコンパイルエラーが出ます。デストラクタデストラクタが呼ばれるというのは自分がスコープから外れる直前です。これは自分自身がリンクリストから外れる事を意味します。この時、もし自分の両隣にすでに誰もいなければ、それは自分が最後の一人である事を示唆していますから、保持しているオブジェクトポインタをdeleteします。*)このくらいでしょうか。※if文での判定などで使います。}};参照演算子については注意が必要です。ptrがNULLの場合があるためです。何も考えずに参照するとメモリ保護違反で落ちます。スマートポインタと言えどもNULLチェックは必要であるという事です。落とし穴回避:自分自身の代入はリンクしない参照カウンタ方式にしろリンクリスト方式にしろ、スマートポインタには幾つか落とし穴があります。基本的には「自己参照はやばい」です。リンクリスト方式のスマートポインタでは自分自身の分身を自分にリンクしてしまうとやばいです。やばくなるか?例えばまだリンクしていないスマートポインタオブジェクトOがあるとします。これにO自身を代入しようとすると、最初にOは自分自身が他人になるので自分の保持しているポインタが解放できるかをチェックします。今の場合は誰もリンクしていないので解放してしまいます。「もう解放してしまった自分自身」を代入します。この結果、ダングリングしたオブジェクトを指すポインタを保持する人ができてしまいます。リンクリスト中に自分自身がいる場合は問題ありません。ABCD-O-EFGとある所にOを追加する場合、Oはまず自分自身をリストから外してからリストに加わります。結果としてO意外に加わる時にはその右側に位置が移るだけです。Oに追加しようとすると外れてしまう段階でOが独立してしまうため、上のリンクしていないスマートポインタオブジェクトへの代入と同じことになってしまいます。文章だけだとよく分からないと思いますが(^-^;、兎にも角にも自分自身への代入は残念な結果になります。これを防ぐには、代入時に自分自身であるかどうかをチェックするだけです。自分自身だったら何もしない、ただそれだけです。それが大変に重要になります。*this;}以上の実装を踏まえたリンクリスト方式のスマートポインタはツール編に公開致します。