C++ exceptional Maximally Reusable Generic Containers

7832 ワード

この章では,テンプレートコピー構造関数とテンプレート付与構造関数を用いてコンテナを最大化する方法について主に述べた.本には、固定サイズを実現するvectorの例が示されています.コードは以下の通りです.
template<typename T, size_t size>
class fixed_vector
{
public:
    typedef T*       iterator;
    typedef const T* const_iterator;
    //      
    fixed_vector(){}
    //        
    template<typename U, size_t usize>
    fixed_vector(const fixed_vector& other)
    {
        copy(other.begin(),
             other.begin() + min(size, usize),
             begin());
    }
    //        
    template<typename U, size_t usize>
    fixed_vector&
    operator=(const fixed_vector& other)
    {
        copy( other.begin(),
              other.begin + min(size, usize),
              begin());
              return *this;
    }
    iterator begin()             { return v_; }
    iterator end()               { return v_ + size; }
    const_iterator begin() const { return v_; }
    const_iterator end()   const { return v_ + size; }

private:
    T v_[size]; 
}

テンプレートコピーコンストラクタとテンプレート付与コンストラクタを用いて、TとUが互いに変換可能である場合、異なるタイプのfixed_vectorfixed_vectorを付与することができる.しかし、ここでは、テンプレートコピーコンストラクション関数と付与コピーコンストラクション関数がコピーコンストラクション関数と付与コンストラクション関数とは異なるという問題に注意してください.すなわち、テンプレート形式のコピーコンストラクタと付与コンストラクタを書くと、コンパイラはデフォルトのコピーコンストラクタと付与コンストラクタを暗黙的に生成します.すなわち、2つの内部タイプが一致するfixed_vectorが付与されると、テンプレート形式のコピーコンストラクタおよび付与コンストラクタは呼び出されない.つまりTとUは同じタイプではありません.
また,stlでこのようなテンプレートコピー構造と付与構造を実現する方法について述べた.STLで使用するiter rangeの方式は、以下の通りである.
template<class RAIter>
fixed_vecotr(RAIter first, RAIter last)
{
    copy(first,
         first + min(size, (size_t)last - first),
         begin());
}

template<class Iter>
fixed_vector&
assign(Iter first, Iter last)
{
    copy(first,
         first + min(size, (size_t)last - first),
         begin());
    return *this;
}

最後に、非常に重要な点は、異常な安全問題です.上記のfixed_vectorクラスのテンプレートコピー構造もテンプレート付与構造も異常に安全ではありません.まず付与構造関数で異常セキュリティを実現するには,Temp−Swap法を用いることが一般的であり,まず伝達された別のオブジェクトを用いて一時的なオブジェクトを構築し,その後,一時的なオブジェクトの内容とそのオブジェクトの内容を交換し,この交換関数を求めるには異常を投げ出すことができない.これにより,一時オブジェクトを構築する際に異常が発生しても,そのオブジェクトの状態情報は変わらない.以上のクラスの設計は,どのようにswap関数,すなわち交換関数を設計しても,異常を投げ出さないことは保証できない.swap過程では必ずT型オブジェクトの構造が伴うが,T型オブジェクトの構造では異常を投げ出さないことは保証できないからである.したがって,以上のクラスの設計を修正し,T配列タイプのプライベート変数を1つのポインタに変更する必要があり,swap関数はこのポインタを交換するだけでよいが,1つのポインタを交換することで異常を投げ出さないことを保証できる.具体的には、次のようになります.
// A strongly exception-safe version:
//
template<typename T, size_t size>
class fixed_vector
{
public:
    typedef T*       iterator;
    typedef const T* const_iterator;

    fixed_vector() : v_(new T[size]) {}
    ~fixed_vector() { delete [] v_; }
    //               
    fixed_vector(const fixed_vector& other)
    :v_(new T[size])
    {
        try
        {
            copy(other.begin(),
                 other.end(),
                 begin());
        }
        catch(...)
        {
            delete [] v_;
            throw;
        }
    }
    //               
    template<typename U, size_t usize>
    fixed_vector(const fixed_vector& other)
    :v_(new T[size])
    {
        try
        {
            copy(other.begin(), 
                 other.begin() + min(size, usize),
                 begin());
        }
        catch(...)
        {
            delete [] v_;
            throw;
        }
    }
    //        Swap      
    void Swap( fixed_vector& other) throw()
    {
        swap(v_, other.v_);
    }
    //                 ,   Temp-Swap  
    fixed_vector& operator=(
    const fixed_vector& other)
    {
        fixed_vector temp(other);
        Swap(temp);
        return *this;
    }
    //                  ,   Temp-Swap  。
    template<typename U, size_t usize>
    fixed_vector& operator=(
    const fixed_vector& other)
    {
        fixed_vector temp(other);
        Swap(temp);
        return *this;
    }
    iterator       begin()       { return v_; }
    iterator       end()         { return v_ + size; }
    const_iterator begin() const { return v_; }
    const_iterator end()   const { return v_ + size; }
private:
    T* v_;
}

このクラスの実装と上記のクラスの実装の最初の違いは、このクラスが異常に安全であることです.2つ目の違いは、このクラスには、テンプレートコピー構造関数とテンプレート付与構造関数とは異なるコピー構造関数と付与構造関数が1つずつ追加されていることです.追加しないと、コンパイラがデフォルトで生成したコピー構造と付与構造がクラス内のポインタを直接コピーするため、これは完全に間違っています.
小結:この章では、以下の点を覚えておきます.コピーコンストラクション関数、付与コンストラクション関数、テンプレートコピーコンストラクション関数、テンプレート付与コンストラクション関数の違い.2.異常に安全なクラスを実現する方法.Temp-Swapの方式はいったいどのように実現されたのか.