ポインタクラスは有名なところではboost
ライブラリのscoped_ptr
クラスや、shared_ptr
クラスがありますが、ここではscpl
ライブラリで用意しているsmart_ptr
、managed_ptr
、auto_ptr
各クラスとの違いなどを紹介したいと思います。
ただの宣伝になってしまうかもしれませんが(笑)。
09/05/18 に「ポインタクラスの役割」について、内容の間違いを修正・追書しました。間違いを指摘して頂いた方に感謝いたします。
目次
ポインタクラスの役割
ポインタクラスは、ポインタを直に扱うC/C++言語特有のユーティリティクラスです。
例えば、関数内で一時的にデータやクラスをnew
で作成したときなどで、途中で問題が発生したとき等に解放し忘れしてしまうことがあります。このときにポインタクラスを使うと、そのクラスが解放されると包んでいるデータポインタも一緒に解放してくれるものです。
boost::scoped_ptr
は上記そのままの動作をする処理に対応しており、複数で参照や途中でのポインタの代入などには対応していません。
(標準C++ライブラリにはstd::auto_ptr
がありますが、途中でポインタを代入するときに直感的に扱えないという問題点があります)
boost::shared_ptr
クラスは、参照カウントを備えており、参照先(参照カウント)が無くなったら解放するような構造になっています。
boost::shared_ptr
クラスは下記のような方法で使用します。
// インクルードやクラス宣言などは省略 void func(){ // 新しいポインタの準備し、即座にポインタクラスに入れる boost::shared_ptr<Cls> ptr(new Cls); // ここで何らかの処理を行う } // 関数から出るとき、 ptr は解放され、 // 参照先が無いときに上記で new したクラスが解放される
変数をポインタ型からポインタクラスに変えれば、解放し忘れによるメモリリークのほとんどを防げます。
ただし、下記のような循環参照が発生する場合にはshared_ptr
クラスは使えません。
// 循環参照の例 struct Cls; typedef boost::shared_ptr<Cls> ClsPtr; struct Cls{ ClsPtr _to; }; int main(){ ClsPtr a(new Cls()); ClsPtr b(new Cls()); ClsPtr c(new Cls()); a->_to = b; b->_to = c; c->_to = a; return 0; } // 上記 a, b, c の持つクラス全てが解放されない。
このとき、実行上はエラーは発生しませんが、メモリリークが発生します。
a
はb
、b
はc
、c
はa
と、循環された参照になっているため、正しく解放されない結果となります。
この循環参照の問題は参照カウントを使用したポインタクラスでは解決することが出来ません。
この問題を解決するには、GC
(ガーベジコレクタ)のような、参照されていないポインタを解放するような処理が必要となりますが、ここでは触れません。
boostライブラリのポインタクラスの欠点
ほぼ万全に準備されているboost
ライブラリのポインタクラス群ですが、唯一の欠点が存在します。
それは、コンストラクタや代入で2回以上同じポインタを渡してはいけないと言うものです。
boost
ライブラリのドキュメントでは、下記の記法を使用することにより唯一のポインタを渡すことが証明されています。
boost::scoped_ptr<Cls> ptr(new Cls);
ただし、この記法ができない場所があります。それは、コンストラクタ内で自分自身(this
)をポインタクラスに含めようとすることです。
プログラムの書き方によっては回避できる問題ですが、速度重視のプログラムの書き方などではこの問題に遭うようです(実際自分のライブラリで起きた問題でもある)。
それを解消するために作ったのがscpl
ライブラリのmanaged_ptr
クラスです。
使い方などは下に記述します。
smart_ptr
ここからscpl
ライブラリのポインタクラスについて説明します。
smart_ptr
クラスは、boost
ライブラリのscoped_ptr
クラスとほぼ同じ処理を行いますが、よりシンプルにクラスを設計しているため、コンパクトにコンパイルできます。
また、解放時の処理を型別に行うことができ、柔軟に使用することができます。
managed_ptr
boost
ライブラリの上記問題を解決するために、smart_ptr
クラスを拡張させ、ポインタからのポインタクラスの作成に対応させたタイプです。
ポインタテーブルを使用しているため、沢山のポインタをmanaged_ptr
クラスで使用する場合、検索のための処理が長くなるため、必要なポインタのみmanaged_ptr
クラスを使用してください。
このクラスも、解放時の処理を型別に行うことができます。
auto_ptr
auto_ptr
クラスは、一時的なインスタンスデータの使用に特化したクラスです(機能としては、std:auto_ptr
、boost::scoped_ptr
と同等です)。
class Cls1{ Cls1(); }; class Cls2{ Cls2(); }; class Cls3{ Cls3(); }; struct Data{ Cls1* cls1; Cls2* cls2; Cls3* cls3; }; Data* init(){ Data* data = new Data; if(!data) return NULL; data->cls1 = new Cls1(); if(!data->cls1){ delete data; return NULL; } data->cls2 = new Cls2(); if(!data->cls2){ delete data1; delete data; return NULL; } data->cls3 = new Cls3(); if(!data->cls3){ delete data1; delete data2; delete data; return NULL; } return data; }
上記のような処理のように、長文化しやすい初期化処理です(他の方法で少しは短く出来る)が、次のようにauto_ptr
クラスを使った方法だと解放処理を省略できます。
class Cls1{ Cls1(); }; class Cls2{ Cls2(); }; class Cls3{ Cls3(); }; struct Data{ Cls1* cls1; Cls2* cls2; Cls3* cls3; }; Data* init(){ scpl::auto_ptr<Data> data(new Data()); if(!data) return NULL; scpl::auto_ptr<Cls1> a(new Cls1()); if(!a) return NULL; scpl::auto_ptr<Cls2> b(new Cls2()); if(!b) return NULL; scpl::auto_ptr<Cls3> c(new Cls3()); if(!c) return NULL; // 関数オブジェクトとして呼び出し、 // 解放する必要の無いことを証明する。 data->cls1 = a(); data->cls2 = b(); data->cls3 = c(); return data; }
このように省略化ができ、解放のし忘れも防げます。
もちろんこのクラスも、解放時の処理を型別に行うことができます。