2010年12月28日火曜日

スマートポインタについて。 なんか別にいらないやって思っている人がいたら、つかったらいいと思うよ的な紹介

自分はC++しか使えませんが、そんなに言語に精通していないので自分がtwitterでフォローしている人たちとは、次元の違ったもっと厳密ではない部分で紹介的な感じで書きます。
いわゆるスマートポインタC++に関してです。
自分の認識ではdeleteを記述しなくて良くなる大変精神衛生上いいものです。
deleteをしなくていいということはつまり無効なアドレスアクセスを行わないために、やたらと生存期間を気にしたり、そのオブジェクトを管理している責任者の為にわざわざ変なルートでオブジェクトの生死の要求経路を作らなくていいので、精神衛生上いいのです。
こんな風に使います。
リファレンスポインタ
SPtr<test> spTest = new test;
spTest->x = xxx;
{
  SPtr<test> spTest2 = spTest;
  // spTest消えない
}
spTest = NULL; // 参照なくなって消える
spTest2とspTestは同じ物を操作でき、spTest2はほったらかしでスコープ抜けて安全です。でspTestにNULL入れてるんでspTestに何かすることはもう無いですよね。まあNULLチェックするでしょうからそれで問題ないですね。
ここで、もう必要なくなったので自動的にdeleteされます。
自分ははじめそのスマートポインタの挙動を説明してもらったときに、そんな都合のいいことあるかい!と思いました。絶対危険だとも思いました。
ですが、あるプロジェクトであるライブラリを使わざる負えなかったときにスマートポインタを使わざる終えなかったので、実装を見て、デバッガで幾度と無く動作を追うことになりました。
そしたら、使っていて問題になることはほとんどありませんでした。
大丈夫です。
自分が使ってきたスマートインタの実装を解説します。いわゆるリファレンスカウンタ形式です。
この実装のアイデアはすごく感心したのですが、要点は
「newなんかで使うポインタではなく、実体のオブジェクトはスコープから抜けるとデストラクタが呼ばれる」ってところでしょうか。
オート変数みたいな、いきなり宣言するオブジェクトCTest test;とかです。
これってスコープ抜けると溶けてなくなるんですが、もちろんその際にデストラクタが呼ばれます。ですので、この時がチャンスです。デストラク タが呼ばれるタイミングが何かがなくなるタイミングなので、その時に参照カウンタと言うものを減らしてカウンタが0になったら、必要ないと判断するチャン スです。こんだけですね。
ポインタと言っても実はポインタを格納したオブジェクトを作っているだけです。
ですので、実際にnewしたポインタは箱に入っていて、その箱がうまいことやってる感じです。いろいろ演算子をオーバーライドしていかにもポインタっぽく扱えるようにしている感じですか。さらに実装を見ればわかるのですが参照カウンタはスマートポインタ側ではなくスマートされる方に、持ちたいので参照カウンタをあれこれするクラスを継承しておきます。
つまり、
SPtr<test> spTest = new test;
での自前クラスtestは
class test :public ReferenceObject
{
みたいに定義しておきます。
実装は以下の雰囲気です。自分で作ってみると結構理解できて使ってみる気になるんじゃないでしょうか。
templateってのは実際に実装を使用しないとコンパイル走らないので、使っているうちにコンパイル通らないシチュエイションが出てきたら、実装を変えたり追加していけば、そのうち大体の使用に耐えるものになるんじゃないでしょうか。
自分はそうしてまとめていきました。メカニズムの説明用で以下一部抜粋ですので、あらゆる状況はカバーできていません。
まず、スマートポインタと言うなの箱クラス
template <typename T> 
class SPtr
{
protected:
 template<typename O> friend class RefPtr;
protected:
    T* m_pObject; // これが実際にnewされたものを指すポインタになります

public:
    inline SPtr(T* pObject = (T*) 0) // 作ったりしたときにカウンタを増やす
    {
     m_pObject = pObject;
     if (m_pObject)
         m_pObject->AddRef();
    }
    inline SPtr(const SPtr<T>& ptr)
    {
     m_pObject = ptr.m_pObject;
     if (m_pObject)
         m_pObject->AddRef();
    }
    inline ~SPtr() // スコープから抜ける時なんかの消えるときにカウンタを減らす
    {
     if (m_pObject)
         m_pObject->DelRef();
    }
ポインタみたいに扱うための演算子オーバーライド
    inline operator T*() const 
    {
     return m_pObject;
    }
    inline T* operator->() const
    {
     return m_pObject;
    }
代入で中身を共有して参照カウンタ増やす処理
ここが結構みそですでに持っているやつのカウンタを減らして
新しく来たやつのカウンタを増やす。
この時代入がNULLだったら前のやつのカウンタを減らすだけになるのでつまり
spTest = NULL;とすることで参照カウンタを減らす処理になる。
inline SPtr<T>& operator=(const SPtr<T>& ptr)
    {
     if (m_pObject != ptr.m_pObject)
     {
         if (m_pObject)
             m_pObject->DelRef();
         m_pObject = ptr.m_pObject;
         if (m_pObject)
             m_pObject->AddRef();
     }
     return *this;
    }
これが参照カウンタをもっている継承してほしいクラス
カウンターを持っていてインクリメントとデクリメント命令があり
デクリメント命令でカウンタが0になればもう誰からも必要とされていないので自分を殺す。これがdeleteを自動で呼んでくれるところ。 delete thisで実現しているんですね。
struct ReferenceObject
{
private:
 mutable unsigned int m_uiRefCount;    // 権利を持っている人の数
public:
 ReferenceObject()
 : m_uiRefCount(0)
 {
 }

 virtual ~ReferenceObject();
    inline unsigned int GetRefCount() const { return m_uiRefCount; }

    inline void AddRef() const 
 {
  m_uiRefCount++;
 }
    inline void DelRef() const
    {
     m_uiRefCount--;
  if(m_uiRefCount==0) delete this;
    }
    
};
うん、ブログめんどくさい。
箇条書きなので、あとでちゃんとまとめよう。
次にウィークポインタ書こう

いつも未来の輪郭線描くまっすぐな定規は

いくつもの昨日の 勇気でできてる