Effective C++ (7): Templates and Generic Programming


Introduction
この章は主にテンプレートのプログラミングを紹介し、最も核心的な思想はできるだけ運行期間のしたことをコンパイル期間の完成まで移動することである.最後に以下のTMPについても簡単に紹介した.
Rule 41: Understand implicit interfaces and compile-time polymorphism
暗黙的なプログラミングとコンパイル期間の多様性を理解する.1つのRuleについて理解する必要があるのはtemplateのマルチステートがclassesのマルチステートと異なることであり、1つは発生とコンパイル期間であり、1つは運行期間である.効率的にはtemplateの方が速い.しかしtemplateがインタフェースの一致を保つには暗黙的(implicit)であり、すなわちtemplate関数を書く場合、w.size()のような関数を用いると、実際には暗黙的にこのtypeにsizeというインタフェース関数が必要となる.コードを書くのも難しくなりました
Remeber:
  • classesおよびtemplatesは、インタフェースおよびマルチステート
  • をサポートします.
  • classesにとってインタフェースは明示的であり、関数署名を中心とする.マルチステートはvirtual関数によって実行期間
  • で発生する.
  • templateパラメータでは、インタフェースは暗黙的(implicit)であり、有効な式に基づいて、マルチステートはtemplateの具体化と関数の再ロード解析によってコンパイル期間
  • で発生する.
    Rule 42: Understand the two meanings of typename
    コードを見ればいいです.
    template  //template   class     
    void print2nd(const C& container)
    {
        typename C::const_iterator iter(container.begin()); //        ,        typename
    }
    

    Remeber:
  • templateパラメータを宣言する場合、class=typename
  • キーワードtypenameを使用してネストされた依存タイプ名を表しますが、base class lists(ベースクラス列)またはmember initialization list(メンバー初期値列)内でbase class修飾子
  • として使用することはできません.
    Rule 43: Know how to access names in templatized base classes base class templatesを継承する場合は、base class templatesのメンバーを「this->this」で呼び出す必要があります.例:
    template
    class MsgSender{
    public:
        ...
        void sendClear(const MsgInfo& info)
        {
            std::string msg;
            Company c;
            c.sendCleartext(msg);
        }
        void sendSecret(const MsgInfo& info)
        { ... }
    };
    
    template
    class LoggingMsgSender: public MsgSender
    {
    public:
        void sendClearMsg(const MsgInfo &info)
        {
            ...
            sendClear(info);  //       
            this->sendClear(info);  //     
            ...
        }
    };
    

    これはコンパイラがその中のCompanyにsendClearという関数があるかどうかを判断できないためである(特化の問題を考慮).したがって、base class templateの任意の特化バージョンが一般的な(汎化)バージョンで提供されるインタフェースをサポートすることをコンパイラに承諾するには、「this->」を追加する必要があります.
    「this->」の他にも2つの方法があります.
  • using宣言式、例えばusing MsgSender::sendClear
  • は、MsgSender::sendClear(info)のような明確な資格修飾子を用いる.「virtualバインド動作」
  • を閉じるため、この方法は推奨されません.
    Remeber:template classを継承する場合は、「this->」でbase class templates内のメンバー名を呼び出すか、「base class資格修飾子」と書くことで完了します.
    Rule 44: Factor parameter-independent code out of templates
    templateの使用はコードの膨張の問題を引き起こす可能性がある.特に、非タイプパラメータ(non-type parameter)については、次のように注意してください.
    template
    class SquareMatrix{
    public:
        ...
        void invert();
    };
    

    上のsize_t nは非タイプのパラメータであり、nが5,10をとると、定数5と10が異なる以外は2つのコードが生成する、それによってコードが膨張しやすい.この問題を解決する最善の方法は、操作されたコードを1つのbase template classに配置し、データの問題がある場合はbase template classにデータを指すポインタを保存し、本当のデータをderived template classに配置し、derived classは非タイプパラメータを受け入れ、derived template classにはデータを保存する以外に、他の関数はbaseの関数を直接呼び出すので、重複するコードにはderived classのコードしかありません.例:
    template        //       
    class SquareMatrixBase{
    protected:
        SquareMatrixBase(std::size_t n, T* pMem):
            size(n), pData(pMem) {}
        void setDataPtr(T* ptr) { pData = ptr; }
        void invert(std::size_t matrixSize);
        ...
    private:
        std::size_t size;
        T* pData;   //       
    };
    
    template
    class SquareMatrix: private SquareMatrixBase{
    public:
        SquareMatrix() : SquareMatrixBase(n, data) {}
        ...
    private:
        T data[n*n];
    };
    

    このようなnon-type template parametersに加えて、type template parametersも膨張を引き起こす.例えばポインタを含む場合はlistlistであるが、代わりにvoid*を用いることが望ましい.
    Remeber:
  • Templatesはパラメータに基づいて複数のコードを生成し、コード冗長性
  • をもたらしやすい.
  • 非タイプテンプレートパラメータ(non-type template parameters)によるコード膨張は、templateパラメータを関数パラメータまたはclassメンバー変数で置き換えることができる.
  • タイプパラメータ(type parameters)によるコード膨張は、完全に同じバイナリ記述(binary representation)を有する表現タイプ共有実装コード
  • を実現することができる.
    Rule 45: Use member function templates to accept “all compatible types”
    メンバー関数テンプレートを使用してすべての互換性タイプを受信
    例を挙げると、次のようになります.
    template
    class shared_ptr{
    public:
        template
            explicit shared_ptr(Y* p);  //        
        template
            shared_ptr(shared_ptr const& r); //       
        ...
    };
    

    これがテンプレートクラスで定義汎化テンプレート関数である.
    Remeber:
  • member function templates(メンバー関数テンプレート)を使用して、「すべての互換性タイプを受信可能」関数
  • を生成してください.
  • 「汎用copy構造」または「汎用assignment操作」に使用するmember templatesを宣言した場合は、通常のcopy構造関数とcopy assignmentオペレータ
  • を宣言する必要があります.
    Rule 46: Define non-member functions inside templates when type conversions are desired
    前述のように、「すべてのパラメータのタイプ変換」の関数については、non-member functionとして、暗黙的なタイプ変換を提供する.しかしtemplate classには違いがある.関数をfriend関数として定義し、クラス内で実装する必要があります.
    template
    class Ration{
    public:
        ...
        friend const Rational operator*(const Rational& lhs, const Rational& rhs)    //     friend   
        {
            return Rational(lhs.numerator() * rhs.numerator(),
                        lhs.denominator() * rhs.denominator());
        }   //       
    };
    

    Remeber:class templateを作成するとき、彼が提供した関数が「すべてのパラメータの暗黙的なタイプ変換」をサポートしている場合は、それらの関数を「class template内部のfriend関数」と定義してください.
    Rule 47: Use traits classes for information and types
    まず、iteratorの5つの異なるタイプを導入します.
  • input_iterator:一方向で、毎回
  • しか操作できません.
  • output_iterator:同上
  • forward_iterator:前の2つの反復器のいずれかを行うことができ、
  • を複数回操作することができます.
  • bidirectional_iterator:双方向操作可能
  • random_access_iteratror:最も強力で、定数時間で勝手にジャンプ
  • はい、次にTraints classes+関数のリロードを紹介して、次の問題を解決します.iteratorをジャンプさせる関数を書く必要があります.異なるiteratorタイプによって異なる実装があります.
    template
    void advance(IterT &iter, DistT d);
    

    では、2つの問題を解決します.1.IterTがどのタイプのiteratorかをどう判断するか.2.コンパイラでタイプによって実装が異なる場合
    この場合、私たちはTraits classesを採用してiteratorにタイプ情報を挟む.簡単に言えばiteratorを定義することですtraitsは、タイプ情報を伝達し、各iterator自身でtag情報を定義し、iterator_traitsはこのtagの情報を伝えることです
    template
    struct iterator_traits{     // IterT   iterator_category          "IterT        ",     iterator_traits        
        typedef typename IterT::iterator_category iterator_category;  // typedef typename            ,     
        ...
    };
    
    /*                  */
    template
    struct iterator_traits{ 
        typedef random_access_iterator_tag iterator_category;
        ...
    };
    
    template <...>
    class deque{
    public:
        class iterator{
        public:
            typedef random_access_iterator_tag iterator_category; //        , random_access_iterator_tag     struct
            ...
        };
        ...
    };
    
    /*            doAdvance   traits  ,          advance   traits   */
    template
    void advance(IterT &iter, DistT d)
    {
        doAdvance(iter, d, 
            typename std::iterator_traits::iterator_category());
    }
    
    template
    void doAdvance(IterT &iter, DistT d,
                        std::random_access_iterator_tag)
    {
        iter += d; 
    }
    
    template
    void doAdvance(IterT &iter, DistT d,
                        std::bidirectional_iterator_tag)
    {
        if ( d >= 0) { while (d--) ++iter; }
        else { while(d++) --iter; }
    }
    

    Remeber:
  • Traits classesは、「タイプ関連情報」をコンパイル期間中に利用可能にする.彼らはtemplatesと「templates特化」で
  • を実現した.
  • リロード・テクノロジー(overloading)を統合すると、traints classesはコンパイル中にタイプにif...elseテスト
  • を実行する可能性があります.
    Rule 48: Be aware of template metaprogramming
    テンプレートメタプログラミング(TMP)は図霊完備であることが証明され、以前Rule 47はTMPの一例であったが、TMPは面倒で直感的ではないため、ここではTMPのHello worldを簡単に紹介し、factorialを求める.
    template
    struct Factorial{
        enum { value = n * Factorial::value };
    };
    
    template<>          //    
    struct Factorial<0>{
        enum { value = 1 };
    };
    
    int main(){
        std::cout<<< Factorial<10>::value ;
    }
    

    Remeber:Template metaprogramming(TMP、テンプレートメタプログラミング)は、実行期間からコンパイル期間に作業を移すことができ、早期のエラー検出とより高い実行効率を実現することができます.
    シリーズ記事
    Effective C++ (1): Accustoming Yourself to C++ Effective C++ (2): Constructors, Destructors, and Assignment Operators Effective C++ (3): Resource Management Effective C++ (4): Designs and Declaration Effective C++ (5): Implementation Effective C++ (6): Inheritance and Oject-Oritent Design Effective C++ (7): Templates and Generic Programming Effective C++ (8): Customizing new and delete Effective C++ (9): Miscellany