c++スマートポインタのauto_を探るptr_refの存在意義

9043 ワード

最近侯捷さんが翻訳した「C++標準ライブラリ」を拝読し始め、スマートポインタの最後のauto_ptr_refの存在は少しぼんやりしていて、本の上でその存在意義をはっきり言っていないような気がして、午後の時間をかけて関連資料を調べて、やっと少しはっきりしました.ここで皆さんがこの本を読んだことがあるとしますauto_ptrに関する知識は、よく分からない場合は、この本の第4章の関連部分を読むことができます.
わかってるよptrの大きな特徴は、部下データに対する唯一の制御権、すなわち複数のauto_を許可しないことである.ptrはauto_ptrが削除されると、その制御データも削除されます.このとき、他のauto_を参照します.ptrでは深刻な問題が発生するためauto_ptrは,レプリケーション構造や付与を行う際に,制御権遷移という操作を行う.次のコードを見てください.
auto_ptr<int> p(new int(3));
auto_ptr<int> q(p);

cout << "p = " << p.get() << " q = " << q.get() << endl;

最終出力の場合、pの結果は0であり、qの結果は3のアドレス(getメソッドで得られるのはデータのアドレス)であり、これは、複製構造の過程でpがデータの制御権をqに渡したため、pの中が空であることを示している.同様に、割り当て操作は同じ結果をもたらします.
auto_ptr<int> p(new int(3));
auto_ptr<int> q = p;

cout << "p = " << p.get() << " q = " << q.get() << endl;

現在のすべてのコードauto_ptrはすべて左の値として現れ、auto_ptrが右の値として現れるとどのような状況になりますか?たとえば、次のようなコードがあります.
auto_ptr<int> p(auto_ptr<int>(new int(3)));

もしauto_がなかったらptr_refでは,標準c++環境でコンパイルを行うが,このコードはコンパイルすらできないのは,非常量の参照で右値を作ることができないためである.これはc++設計の初めに規定されたもので、右の値が非常に量の参照(references-to-non-const)であれば、一部のプログラマーたちが見たくない状況が発生するためです.(具体的にはMore Effective c++のItem 19を参照できますが、一部の友人が見つからない可能性がある場合は、文章の最後に説明しました.
そしてauto_ptrの設計過程では,制御権移行の問題を考慮して,レプリケーション構造や付与値の再ロードを行う際に右値がauto_ではない.ptr&const rshではなくauto_ptr&rshは、c++STLの関連ソースコードを以下に示す.
typedef auto_ptr<_Ty> _Myt;

explicit auto_ptr(_Ty *_Ptr = 0) _THROW0()
    : _Myptr(_Ptr)
    {    // construct from object pointer
    }

auto_ptr(_Myt& _Right) _THROW0()
    : _Myptr(_Right.release())
    {    // construct by assuming pointer from _Right    auto_ptr
    }

定義auto_ptrのレプリケーションコンストラクタではconst宣言は使用されません.これは右値のrelease()関数をスムーズに呼び出し、右値のデータ制御権を解放するために、release()はデータのポインタを返し、左値に渡して管理します.そしてauto_ptr_refの存在はこの困った問題を解決するためであり,中間左値としてこの問題を解決した.(c++のデザイナーたちがこの方法を考えていたら、stlにauto_ptrというものは存在しないかもしれません)
理解しやすいようにptr_ref、ここでauto_ptr_refのSTLでの定義コード:
// TEMPLATE CLASS auto_ptr
template<class _Ty>
    class auto_ptr;

template<class _Ty>
    struct auto_ptr_ref
        {    // proxy reference for auto_ptr copying
    explicit auto_ptr_ref(_Ty *_Right)
        : _Ref(_Right)
        {    // construct from generic pointer to auto_ptr ptr
        }

    _Ty *_Ref;    // generic pointer to auto_ptr ptr
    };

構造は非常に簡単で、データポインタが1つしか保存されておらず、その構造関数はexplicit(構造を表示)として宣言され、c++の設計者たちはauto_ptrには、この方法が追加されています.
template<class _Other>
        operator auto_ptr_ref<_Other>() _THROW0()
        {    // convert to compatible auto_ptr_ref
        _Other *_Cvtptr = _Myptr;    // test implicit conversion
        auto_ptr_ref<_Other> _Ans(_Cvtptr);
        _Myptr = 0;    // pass ownership to auto_ptr_ref
        return (_Ans);
        }

auto_ptr(auto_ptr_ref<_Ty> _Right) _THROW0()
        {    // construct by assuming pointer from _Right auto_ptr_ref
        _Ty *_Ptr = _Right._Ref;
        _Right._Ref = 0;    // release old
        _Myptr = _Ptr;    // reset this
        }

この2つのコードは重要です.コンパイラのコンパイルプロセスをシミュレートしてみましょう.最初にコンパイルできなかった言葉を例に挙げます.
  auto_ptr<int> p(auto_ptr<int>(new int(3)));

1、 auto_ptr<int>(new int(3)), , , 3
2、 auto_ptr<int> p() , auto_ptr auto_ptr(auto_ptr& const rsh) , ···
3、 , operator auto_ptr_ref<int>() , , , auto_ptr_ref
4、 auto_ptr_ref auto_ptr<int> p() , auto_ptr(auto_ptr_ref<_Ty> _Right),, auto_ptr_ref auto_ptr

   , ··· 。 , :
_Myt& operator=(auto_ptr_ref<_Ty> _Right) _THROW0()
        {    // assign compatible _Right._Ref (assume pointer)
        _Ty *_Ptr = _Right._Ref;
        _Right._Ref = 0;    // release old
        reset(_Ptr);    // set new
        return (*this);
        }

同様の原理で,変換後にconstを迂回してこれを行い,付与操作を完了した.だからこのauto_ptr_refの存在意義は、非常値参照が右値にならないことを避けることによって存在することであることを理解し、auto_ptr_refの存在の意味は少しはっきりします.
 
注意:非常量参照が左の値として使用できない問題について
この記事では、中間変数というものについて触れています.auto_ptrp(auto_ptr(new int(3))という言葉の中で、コンパイラは初めて中間変数を生成し、pの構造に入って試します.ほとんどの人は中間変数をconstとして見ていますが、constタイプなのでauto_にも伝わりませんptrのその非constコピー構造に行きます.
しかし、中間変数というものも付与できる・・・だからもう一つの解釈方法があり、c++は設計の初めに、一つのパラメータが非常値参照の形で方法の内部に入ると、コンパイラはこの値が変更できると信じ、方法が終了した後も、この修正された参照が機能する可能性があると考えられる.修正による予期せぬ誤りを防ぐために,コンパイラは右値が修正できないことを要求し,rsh中のrをreadと解釈すればこの意味をうまく表すことができる.中間変数は1つのパラメータとしてメソッドに渡された後、使い終わったら捨てられ、修正しても意味がない・・・このc++は設計時に中間変数を修正しないことを要求し、いくつかの異常なエラーを避ける.