2011年6月17日金曜日

終・スマートポインタの別の解

さて、前回は weak_ptr は使わずに新しいクラス distributable_ptr を作成しました。実現したいことは、weak_ptr に近いんですが shared_ptr を必要としないというところがミソなわけです。

で、その構造を見てもらうとわかるんですが、参照数をカウントしておいて、なくなったら削除するという仕組みはスマートポインタとまったく一緒です。じゃあ、distributed_ptr は shared_ptr としても機能するのでは?とも考えられます。

で、distributed_ptr に shared_ptr として機能させるためのコンストラクタを追加し、デストラクタにも処理を追加します。
template <class T>
distributed_ptr<T>::distributed_ptr( distributable_ptr<T>* org )
  : data( org->data )
{ // このコンストラクタを使用した場合はスマートポインタモードになります。
  // data が持っている distributable_ptr が最初に +1 した参照数は、この
  // ポインタが参照したことにし、distributable_ptr 自体は weak_ptr のよう
  // に参照数を +1 していないものと考えます。
}

template <class T>
distributed_ptr<T>::~distributed_ptr( void )
{ // スマートポインタモードの時、最後の distributed_ptr が破棄されると、
  // 参照数が 0 になります。
  // スマートポインタモードでなければ、distributable_ptr が参照数 1 を
  // 保持していますので、すべての distributed_ptr が破棄されても参照数は
  // 0 にはなりません。
  T*  ptr = this->data->get();
  if( !distributable_data<T>::dereference( this->data ) )
      delete ptr;
}
この追加によって、distributed_ptr は shared_ptr としても機能することになります。つまり、
class Hoge : public distributable_ptr<Hoge>
{
public :
    Hoge( void ) {}
    virtual ~Hoge( void ) {}
};

void foo()
{
    distributed_ptr<Hoge>  ptr( new Hoge );
    ... 処理 ...
}
のように shared_ptr っぽい書き方をするとスマートポインタとして機能し、関数 foo() を抜ける際にはちゃんと Hoge のインスタンスが破棄されるようになります。当然、前回書いたような機能もそのまま使えます。この distributable_ptr 及び distributed_ptr の特徴としては
・shared_ptr + weak_ptr よりも構築が簡単で安全。
・スタックで確保しても、配列で確保しても、new で確保しても構わない。
・スマートポインタで管理するかどうかは任意に選ぶことができる。
・weak_ptr のように自身のポインタを distributed_ptr として配布する事ができる。
といった所でしょうか。ただ、自身のポインタを外部に配布する必要がない場合には不要となる機能が多いので、shared_ptr をすべてこれに置き換えればいいというものでもありません。

shared_ptr の代わりとして使うには機能がまだまだ足りませんが、いろいろ機能付け足してテストもして後日公開したいと思います。

2011年6月16日木曜日

続々・スマートポインタの問題…というよりも

前回の続きになります。

そもそも、スマートポインタの目的とは、new したものを delete し忘れないようにするためのものでした。今実現したいのは、クラス内から this を安全に外部へ渡したいということで、スマートポインタとは目的が違うような気がします。

つまり、shared_ptr を必要としない weak_ptr があればいいのでは?ということで
// ポインタ情報
template <class T>
class distributable_data
{
public :
    distributable_data( T* ptr_ )
      : ptr( ptr_ ),
        count( 1 )
    {
    }

    long use_count( void ) const
    {
        return this->count;
    }

    void reference( void )
    {
        this->count++;
    }

    T* get( void )
    {
        return this->ptr;
    }

    const T* get( void ) const
    {
        return this->ptr;
    }

    void clear( void )
    {
        this->ptr = 0;
    }

    static bool dereference( distributable_data<T>* data )
    {
        data->count--;
        if( data->use_count() == 0 )
        {
            delete data;
            return false;
        }
        else
        {
            return true;
        }
    }

private :
    T* ptr;
    long count; // ポインタ先ではなく、このクラスの参照数
};
// 配布されるポインタ
template <class T>
class distributed_ptr
{
public :
    distributed_ptr( distributable_data<T>* data_ )
      : data( data_ )
    {
        this->data->reference();
    }

    distributed_ptr( const distributed_ptr<T>& org )
      : data( org.data )
    {
        this->data->reference();
    }

    ~distributed_ptr( void )
    {
        distributable_data<T>::dereference( this->data );
    }

    bool expire( void ) const
    {
        return this->data->get() == 0;
    }

    T* operator->( void )
    {
        return this->data->get();
    }

    const T* operator->( void ) const
    {
        return this->data->get();
    }

private :
    distributable_data<T>* data;
};
// 配布可能ポインタ
template <class T>
class distributable_ptr
{
public :
    distributable_ptr( void )
    {
        this->data = new distributable_data<T>( dynamic_cast<T*>(this) );
    }

    virtual ~distributable_ptr( void )
    {
        if( distributable_data<T>::dereference( this->data ) )
        {
            this->data->clear();
        }
    }

    distributed_ptr<T> lock( void )
    {
        return distributed_ptr<T>( this->data );
    }

private :
    distributable_data<T>* data;
};
こんな感じかな?
※まだ動作検証してません。

で、使うときは、こんな感じに継承して使います。わざわざ継承させるのは、クラスが破棄されたときにコピーされたポインタを無効化するためです。
class Hoge : public distributable_ptr<Hoge>
{
public :
    Hoge( void )
      : distributable_ptr<Hoge>()
    {
    }

    virtual ~Hoge( void )
    {
    }

    void Func( void )
    {
    }
};

void Foo( void )
{
    Hoge  hoge[ 10 ];
    // ポインタ取得
    distributed_ptr<Hoge> ptr = hoge[2].lock();
    // 使用可能か確認してからアクセス
    if( !ptr.expire() )
        ptr->Func();
}
とりあえず、スタック上に配列で確保してみましたが、もちろん new で構築しても問題ありませんし、そのポインタを shared_ptr で管理していても全く問題ないはずです。

distributable_data を構築するのは最初に lock() 関数が呼ばれたときにした方がとか、distributed_ptr のコピー処理とか、スレッドセーフとかも考えるともっといろいろ工夫が必要ですが、とりあえずこんな感じで。しかし、そして、さらに野望が...続く

2011年6月15日水曜日

続・スマートポインタの問題(?

前回の続き。

と、その前に前回少し触れた weak_ptr について軽く説明しておきます。
weak_ptr の元々の用途としては、shared_ptr が指し示すクラスが自身を差すポインタを持つ次のような場合、
class Hoge
{
public :
    shared_ptr<Hoge>  self_ptr;
};

void Foo()
{
    shared_ptr<Hoge> ptr( new Hoge ); // ①
    ptr->self_ptr = ptr;              // ②
}                                     // ③
関数 Foo() を抜けても、構築された Hoge は解放されません。①の時点でポインタの参照数は1です。②でコピーされ、参照数は2になります。そして、③の時点で ptr は解放され、参照数は1になります。つまり、ptr はスコープを抜けて破棄されますが、コピーされた self_ptr は破棄される機会がないのです。

これを解決するために、自身を差すようなポインタを持つ場合は weak_ptr というクラスを使用しましょうということになりました。weak_ptr は参照数を+1しないコピーであり、ここから新しいコピーを作成することもできます。
class Hoge
{
public :
    weak_ptr<Hoge>  self_ptr;
};

void Foo()
{
    shared_ptr<Hoge> ptr( new Hoge );                // ①
    ptr->self_ptr = ptr;                             // ②
    shared_ptr<Hoge> new_ptr = ptr->self_ptr.lock(); // ③
}                                                    // ④
①の時点で参照数は1です。②では、weak_ptr を作っているだけなので、参照数は変わらず1のままです。③では weak_ptr のメンバ関数 lock() を呼ぶことでこのスマートポインタのコピーを作成しているので参照数が2になります。そして、④では ptr 及び new_ptr がスコープから外れるので参照数は-2され0になります。このため、ここで Hogeのポインタは破棄されます。

ただし weak_ptr は参照数を+1しないため、参照数が0になった時点でポインタが破棄され、コピーが作成できなくなります。ですので、weak_ptr が外部にあるときには、正常に取得できたかどうか確認する必要があります。
void Foo()
{
    shared_ptr<Hoge> ptr( new Hoge );       // ①
    weak_ptr<Hoge> wptr( ptr );             // ②
    ptr.reset();                            // ③
    shared_ptr<Hoge> new_ptr = wptr.lock(); // ④
}
①②は前の例とおなじで、参照数はここまでで1です。そして、③の時点で ptr が参照数を-1するので参照数は0になり Hoge は破棄されます。次の④でコピーを作成しようとしますが、ポインタはすでに破棄されていますので、返されるポインタは null になります。ですので、このような使い方をする場合には、取得されたポインタが有効かどうかを確認してから使う必要があります。

さて、この weak_ptr を使えば前回言ったような「外部に渡すためのポインタ」が確保できるようになりますが、これは非常に面倒な作りです。上の例の Hoge は「必ず new で確保しなければならない」「shared_ptr で管理しなければならない」「構築した後、self_ptr を作成しなければならない」という制限がついて回ることになります。これらをまとめて行う関数を作ることはできますが、もし Hoge の派生クラスを作ると、その派生クラス用の構築関数も作る必要が出てきます。また、このクラスの配列を構築したい場合、スタックに構築したい場合にも対応できません。

これらのことから、weak_ptr は良い解決方法とは言えない、と思われるわけです。

…何か長くなってきたので続く

2011年6月14日火曜日

スマートポインタの問題点

C++/CLI という仕様ではガベッジコレクションにより、ポインタ解放の義務がありません。これは、使ったことのある人ならわかると思いますが非常に便利な機能で、必要に応じていくらでもインスタンスを構築し、不要になったらそのまま放置しとけば勝手に裏で解放してくれます。まあ、これはこれで別の問題があるのですが。
標準 C++ にも似たようなことを実現するライブラリがあります。スマートポインタと呼ばれるもので、次期仕様である C++0x に取り込まれる予定で、現在でも boost ライブラリを導入することによってすぐにでも使うことのできる機能です。
void foo()
{
    boost::shared_ptr<Hoge> ptr0( new Hoge );
    // ポインタと同じように使うことができる
    ptr->Func();
    // コピーすると参照カウンタが +1 される
    boost::shared_ptr<Hoge> ptr1 = ptr0;
    // ptr0, ptr1 の両方のインスタンスがなくなると、
    // 自動的に delete される
}

最初は、ポインタをすべてこのような形で使用するよう心がければ、ガベッジコレクションなどなくても C++/CLI と同様にポインタ管理しなくてもよくなる、と考えていました。作られたインスタンスにアクセスするためには、元のスマートポインタまたはそのコピーを使用する必要があるわけですから、不意に解放されたポインタにアクセスしてしまう心配もありません。

しかし、やはりというか、問題がありました。このスマートポインタまたはそのコピーを使用せずにインスタンスにアクセスするケースがあります。それは、そのクラス自身のメンバ関数です。メンバ関数は this  というポインタを使用することによってスマートポインタを経由せずに、いつでも自由にそのインスタンスにアクセスすることができてしまいます。メンバ関数が実行されているということは、そのインスタンスが存在するということですから一見問題ないように思いますが、その this を外部の関数等に渡すことを考えるとどうでしょう?その関数でポインタを保存しておいて、別の機会にアクセスされるとしたら、そのポインタが有効かどうかが別の方法によって知らされない限り不正アクセスが発生する危険性があります。

クラス Hoge に weak_ptr をメンバとして持たせておいて、クラス Hoge を構築する度にそのスマートポインタから weak_ptr を作成しセットする、というのも考えましたがどうにも面倒です。そもそも、クラスを構築するために必ず new で確保しスマートポインタに入れなければならないということを義務づけるのも、あまり好ましくありません。

これらのことから、このスマートポインタだけではガベッジコレクションの代わりはできそうにないな、と結論しました。そして、別の手段を模索することになります。...続く

2011年6月11日土曜日

Radeon HD6750

Left4Dead2 を最近ごりごり遊んでるわけなんですが、ロードがいちいち遅く他の人に出遅れがち、ゲーム開始時には必ず数秒間ラグる、といった不満がありました。まあ、GeForce9600GT とかもうかなり古い世代だよね、新しいのに買い換えよう、ということで買ってきました HD6750。
で、入れ替えたら当然ベンチしたくなるというもの。ためしにモンハンベンチマークをやってみました。

うちのスペック:
CPU: PhenomII X4 965 (3.41GHz)
Mem: 4GB (XP 32bit のため、実質 3.25GB)
M/B: M4A77TD Pro
VGA Driver: Catalyst 11.5

モンハンベンチ結果:
1280x768
1st SCORE:8988
絆 SCORE:6662

まあ、こんなものなのかなと、他の人の結果とかも検索してみる。

orz

なにさ、30000 とか 60000 とか。桁が違いすぎる。何よりショックだったのは、これまで使ってきた 9600GT でも SCORE 8000台出るそうで。買い換えあんま意味なかったのか、ううっ(泣

まあ、メモリも 2GB→3.25GB に増設したおかげもあって、確実にロード時間やらラグやらも減ったので効果はゼロではなかったのでよしとしよう。家の事情で、ファンレスのカードしか選択肢がなかったというのもありますし。