ポインタクラスについて

ポインタクラスは有名なところではboostライブラリのscoped_ptrクラスや、shared_ptrクラスがありますが、ここではscplライブラリで用意しているsmart_ptrmanaged_ptrauto_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 の持つクラス全てが解放されない。

このとき、実行上はエラーは発生しませんが、メモリリークが発生します。
abbccaと、循環された参照になっているため、正しく解放されない結果となります。

この循環参照の問題は参照カウントを使用したポインタクラスでは解決することが出来ません。
この問題を解決するには、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_ptrboost::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;
}

このように省略化ができ、解放のし忘れも防げます。

もちろんこのクラスも、解放時の処理を型別に行うことができます。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

This blog is kept spam free by WP-SpamFree.