C++Primer:第13章:コピー制御のコピー制御と資源管理

4312 ワード

動作イメージ値を定義するクラス
標準ライブラリコンテナやstd::stringのような値のクラスのように、このようなクラスは簡単にこのようなクラスHasPtrを実現することができます.
実現する前に、
stringへのポインタのコピーではなくstringのコピーを完了するコピー構造関数を定義します.
コピー付与演算子を定義し、現在のstringを解放し、右側から新しいstringをコピーします.
stringを解放するための構造関数を定義します
class HasPtr {

public:
	HasPtr(const std::string& s = std::string()) : ps(new std::string(s)), i(0) {}

	HasPtr(const HasPtr& has) : ps(new std::string(*has.ps)), i(has.i) {}
	
	HasPtr& operator = (const HasPtr& has);
	
	~HasPtr() { delete ps; }
private:
	std::string* ps;
	int	i;
};

HasPtr::operatpr = (const HasPtr& has) {
	//                ,                 ,               
	auto newPtr = new std::string(*has.ps);
	//            
	delete ps;
	ps 	= newPtr;
	i 	= has.i;
}

コピー付与演算子の実装では、安全のために右側のオブジェクトをローカル変数にコピーしてから左側のオブジェクトを削除し、左側のオブジェクトにローカル変数を付与する必要があります.左オブジェクトを先に削除すると、右オブジェクトと左オブジェクトが同じオブジェクトの場合、重大なエラーが発生します.
割り当て演算子の場合、左側のオブジェクトと右側のオブジェクトが同じオブジェクトである可能性があるため、左側のオブジェクトが破棄される前に右側のオブジェクトをコピーするのが良いです.
ポインタのような動作を定義するクラス
スマートポインタshared_のような動作ポインタのクラスptr.クラスの動作をポインタのようにする以上、このクラスは構造関数を実行するときに関連するstringを簡単に解放することはできません.このstringを指すオブジェクトが1つしかない場合、このクラスの構造関数でリソースを解放することができます.複数のクラスがこのstringを指す場合、そのうちの1つのクラスがこのstringを解放します.他のクラスは再多重化できません.
だから使う
カウントの方法を参照してこの問題を解決します.
参照カウントの動作は次のとおりです.
  • 初期化オブジェクトを除いて、各コンストラクタ(コピーコンストラクタを除く)は、作成中のオブジェクト共有状態を共有するオブジェクトがどれだけあるかを記録するために参照カウントを作成します.オブジェクトを作成すると、参照カウントは1になります.この場合、オブジェクト共有が1つしかないためです.
  • コピーコンストラクタは、新しい参照カウンタを割り当てる、指定されたオブジェクトのデータメンバーをコピーし、参照カウンタを含む.コピーコンストラクタは、指定されたオブジェクトのより多くの状態が新しいユーザによって共有されることを示す
  • をインクリメント共有する.
  • は、割り当て演算子をコピーして左側の演算オブジェクトの参照カウンタを減算し、右側のオブジェクトの参照カウンタをインクリメントし、左側のオブジェクトの参照カウンタが0の場合、左側のオブジェクトを破棄します.
  • 構造関数は、参照カウントが0であるかどうかを判断し、0である場合、左側のオブジェクトを破棄します.

  • 参照カウントの実装:次の場合を想定します.
    HasPtr h1;
    HasPtr h2(h1);
    HasPtr h3(h1);

    HasPtrはポインタのような動作をするクラスで、新しく作成されたh 1の参照カウントは1であり、h 2を作成し、h 1でh 2を初期化すると、h 1の参照カウント値がインクリメントされ、h 2はh 1の参照カウントを保存し、h 3を作成すると、h 1の参照カウント値がインクリメントされ、h 2の参照カウント値が更新されなければならないので、h 2の参照カウント値は更新できません.したがって、参照カウントをダイナミックメモリに保存する必要があります.これにより、元のオブジェクトと他のコピーオブジェクトが同じカウンタを指し、共有オブジェクトごとに参照カウントのステータスを自動的に更新できます.
    class HasPtr {
    
    public:
    	HasPtr(const std::string& s = std::string()) : ps(new std::string(s)), i(0), use(new size_t(1)) {}
    
    	HasPtr(const HasPtr& has) :  ps(has.ps), i(has.i), use(has.use) { ++ *use;}
    	
    	HasPtr& operator = (const HasPtr& has);
    	
    	~HasPtr();
    private:
    	std::strin g* ps;
    	int	i;
    	size_t* use; //     
    };
    
    HasPtr::HasPtr& operator = (const HasPtr& has) {
    	
    	++ *has.use;
    	if (0 == *use) {
    		delete ps;
    		delete use;
    	}
    	
    	ps 	=	has.ps;
    	i	=	has.i;
    	use	=	has.use;
    	return *this;
    }
    
    HasPtr::~HasPtr() {
    	
    	if (--*use == 0) {
    		delete ps;
    		delete use;
    	}
    }

    HasPtrをコピーするとき、psが指すstringではなくps自体をコピーすることに注意してください.
    スワップ操作
    通常、管理リソースのクラスは、コピー制御メンバーの定義に加えて、交換操作の関数swapも定義されます.理論的には、私たちのswap関数は次のようになります.
    HasPtr temp = 1;
    v1 = v2;
    v2 = temp;

    このようなコードはv 1のstringを2回コピーしましたが、stringのコピーを割り当てるのではなく、swapがポインタを交換する必要はありません.
    std::string* temp = v1.ps;
    v1.ps = v2.ps;
    v2.ps = temp;
    v1.i  =	v2.i;

    swap関数をHasPtrクラスの友元関数として宣言し、swapがHasPtrのpsおよびiメンバーにアクセスできるようにします.
    class HasPtr {
    
    	friend void swap(HasPtr&, HasPtr&);
    	//     
    };
    
    inline
    void swap(HasPtr& lhs, HasPtr& rhs) {
    	using std::swap;
    	swap(lhs.ps, rhs.ps);
    	swap(lhs.i, rhs.i);
    }

    swap関数では、次の操作を行います.
    USing std::swapを使用します.このクラスに独自のswap関数がある場合、一致度は標準ライブラリswapより高くなり、クラス独自のswapが優先され、ない場合は標準ライブラリのswapが使用されます.
    swapではクラスのポインタとintメンバーを交換し、再帰ループは発生しません.HasPtrのデータメンバーは内蔵タイプで、標準ライブラリバージョンのswapが呼び出されます.
    割り当て演算子でswapを使用するには、次の手順に従います.
    通常、swapを定義するクラスは、コピー付与演算子をswap関数で定義します.この演算子は、
    コピーして交換するテクノロジー.
    HasPtr& operator = (HasPtr has) {
    
    	swap(*this, has);
    	return *this;
    }

    HasPtrクラスの賦値演算では、まず右側のオブジェクトをコピー賦値演算子関数にコピーし、左側のオブジェクトのポインタと右側のオブジェクトのポインタを交換します.交換後、右側のオブジェクトは左側のオブジェクトに賦値し、左側のオブジェクトに対応するstringポインタも指します.
    右側のオブジェクトは対応するメンバーをコピーし、右側のオブジェクトのstringポインタは左側のオブジェクトの対応するメンバーを指します.この関数が終了すると、右側のオブジェクトのコピーが破棄され、元の左側のオブジェクトのリソースが解放され、左側のオブジェクトは右側のオブジェクトのメンバーとして保存されます.
    コピーして交換する操作は、以前のコピー付与演算子の実現原理と同じです.
    左側のオブジェクトを変更する前に、右側のオブジェクトをコピーします.このような操作異常の安全を保証した.