C++ベースの異常


stackを使用すると、スタックトップ要素を取るインタフェースが次のように困惑します.
#include <stack>
#include <iostream>

int _tmain(int argc, _TCHAR* argv[])
{
	std::stack<int> si;
	for (int i = 0; i < 10; i++)
	{
		si.push(i);
	}

	while(!si.empty())
	{
		int i = si.top(); //     
		si.pop();         //      
		std::cout << i << std::endl;
	}

	return 0;
}

どうしてint i=siできないのですか.pop(); こんなに便利ですね.答えはstackが共通の標準ライブラリメンバーとして、異常に直面したときに2点をしなければならないことです.
1.異常安全.例外は、それ自体がエラー状態になったり、データが失われたり、リソースが漏洩したりすることはありません.
スタック内部が配列で実現されると仮定し、スタックが空であるかどうかを考慮せず、T i=st.pop()である.のpop()の実現は以下の通りである.
template <typename T>
T stack<T>::pop()
{
	//         data ,top      
	return data[top--];
}

Tのコピーコンストラクション関数が異常(一時オブジェクトがdata[top]で構築されている)を放出すると、オブジェクトの戻りに失敗し、stack内部でtopが位置を変更すると、スタックトップ要素が失われます!
したがって、スタックトップの位置を調整するには、すべてのデータコピーが完了した後、値で返すことは考えられません.
2.異常透明.クライアントコード(格納されたタイプTの実装)が投げ出す異常は、食べられるべきではなく、クライアントに透明に伝えるべきである.
stackの付与演算子を配列で実装すると、データのコピー時にこのようなループが書き込まれます.
template <typename T>
stack<T>& stack<T>::operator=(const stack<T>* rhs)
{
	for (int i = 0; i< rhs.top; i++)
		data[i] = rhs.data[i];
}

Tの付与演算子は,ループが半分になると異常を放出する可能性があり,一部のデータは正常に付与され,もう一部はまだ付与されていない.データの一貫性を保証するにはcatch(...)しか使用できません.例外をキャプチャし、この例外を無視して値を割り当て続けます.(実際には新しい値を古い値に戻すこともできますか?)そうすれば、値を付ける演算法に相当する異常が食べられてしまいます.一般的には、異常は何か起こるべきではないことを意味し、お客様に知ってもらうべきです.
したがって、stackは配列実装を必要としない(しかも配列サイズが固定されており、stackには適していない).stack内部のデータがポインタで格納されている場合、一時バッファを使用してデータを受信し、その過程で異常を処理し、pdataに割り当てられたメモリを指し示す.この方式をcopy&swapと呼ぶ.
// pdata              ,top           
template <typename T>
stack<T>& stack<T>::operator=(const stack<T>& rhs)
{
    T* ptemp = new T[rhs.top];
    try
    {
        for(int i=0; i<rhs.top; i++)
            *(ptemp+i) = *(rhs.pdata+i);
    }
    catch(...)  //          
    {
        delete[] ptemp;
        throw;  //     
    }

    delete[] pdata;  //        
    pdata = ptemp;   //   pdata           
}

戻り値ではなく、例外を使用してエラーの値をレポートします.
1.異常報告エラーを使用して、関数インタフェースの汚染を避けることができる
2.比較的豊富なエラー情報をレポートしたい場合は、単純な戻り値よりも例外オブジェクトを使用する方が効果的であり、複雑なオブジェクトを返すことによるオーバーヘッドを回避します.
3.一部のエラーでは、戻り値が適用されずに報告されます.たとえば、メモリ関数を動的に割り当てます.
たとえば、Cのmalloc関数はNULLを返してダイナミックメモリ割り当てエラーを表しますが、プログラマはmallocの後に戻り値をチェックするのを忘れがちです.C++のnewはメモリの割り当てに失敗した場合std::bad_を放出します.allocなので、int*p=new intを安全に使用できます.返されるポインタp.
4.コンストラクション関数が値を返さないため、コンストラクション関数がエラーを容易に報告できるようにする一般的な手段を提供する.
P.S.構造関数に異常を投げ出さないでください!
evil p = new evil[10];
delete[] p;


10回evilクラスの構造関数を呼び出してリソースを解放します.中間のある構造関数が異常を放出したと仮定すると、上の「stackの付与演算子を配列で実現する」場合と同様に、delete[]はこの異常をキャプチャしないか、異常に従って透明ですが、リソースが解放されず、リソースが漏洩します.この異常を捕獲して後の釈放を続けるか、「異常透明」の原則に違反している.
 
【参考】
Solmyrのエッセイシリーズの7:異常