Effective C++ノート(10)—テンプレートと汎用プログラミング(二)

16392 ワード

条項46:タイプ変換が必要な場合は、テンプレートに非メンバー関数を定義します.
前の例を考慮して、オペレータの再ロード:
class Rational{
public:
    Rational() :x(0){}
    Rational(int x_) :x(x_){}
    Rational &operator*(const Rational& rhs)
    {
        x *= rhs.x;
        return *this;
    }
    int x;
};

int main()
{
    Rational a(2);
    Rational b = a * 2;
    cout << b.x << endl;//ok 4
    //Rational c = 2 * a << endl;//error
    system("pause");
    return 0;
}

条項24では、operator*を非メンバー関数として宣言することを説明します.
class Rational{
public:
    Rational() :x(0){}
    Rational(int x_) :x(x_){}

    int x;
};
const Rational operator*(const Rational &l, const Rational &r)
{
    return Rational(l.x*r.x);
}

int main()
{
    Rational a(2);
    Rational b = a * 2;
    cout << b.x << endl;//ok 4
    Rational c = 3 * a ;//ok
    system("pause");
    return 0;
}

ただし、上記のコードをtemplate形式に変更すると、問題が発生する可能性があります.
template <class T>
class Rational{
public:
    Rational() :x(0){}
    Rational(T x_) :x(x_){}

    T x;
};
template <class T>
const Rational operator*(const Rational &l, const Rational &r)
{
    return Rational(l.x*r.x);
}

int main()
{
    Rational<int> a(2);
    Rational<int> b = a * 2;//error
    system("pause");
    return 0;
}

1つ目は実パラメトリックがRationalであり,これによりTがintとして伝達される2つ目の実パラメトリックタイプはintであり,コンパイラはTタイプを推定することができず,ここでは構造関数の暗黙タイプ変換の条件を備えていない.
解決策は、友元関数を使用することです.
template <class T>
class Rational{
public:
    Rational() :x(0){}
    Rational(T x_) :x(x_){}

    T x;

    friend const Rational operator*(const Rational &l, const Rational &r)
    {
        return Rational(l.x*r.x);
    }

};


int main()
{
    Rational<int> a(2);
    Rational<int> b = a * 2;//ok 4
    cout << b.x << endl;
    system("pause");
    return 0;
}

最初の実パラメトリックRational具現化により対応クラスが出現し,Rationalパラメータを受け入れるfriendも具現化され,暗黙的変換の条件を満たす.
しかし、クラス内で定義するとコンパイラに組み込まれる可能性があります.inlineしたくない場合は、operation*で仕事をしないで、外部関数を呼び出すことができます.
template <class T>
const Rationalfunc(const Rational &l, const Rational &r)
{
    return Rational(l.x*r.x);
}

template <class T>
class Rational{
public:
    Rational() :x(0){}
    Rational(T x_) :x(x_){}

    T x;

    friend const Rational operator*(const Rational &l, const Rational &r)
    {
        return func(l, r);
    }

};


int main()
{
    Rational<int> a(2);
    Rational<int> b = 2 * a;//ok 4
    cout << b.x << endl;
    system("pause");
    return 0;
}

条項47:traits classes表現タイプ情報を使用してください
traitsを見て私はとても親切で、抽出器、先にSTLソースの剖析を勉強したとき、これに対してまだ理解していました.
STLを振り返る反復器:反復器タイプ(iterator_category)は反復器の5つのタイプの1つである:反復器は5種類に分けられる:
Input :   
Output :   
Forward :    
Bidirectional:      
Random Access:       
is-aの関係で表す
//    
struct input_iterator_tag {};
struct output_iterator_tag {};
struct forward_iterator_tag : public input_iterator_tag {};
struct bidirectional_iterator_tag : public forward_iterator_tag {};
struct random_access_iterator_tag : public bidirectional_iterator_tag {};

なぜここはtagなのか、実はSTLでは1つのタイプでマークすればいいだけです.Effectivec++では、advanceの例を挙げると、advance関数はit+=dの動作を実行するために使用されるが、random_access以外の反復器タイプは+=をサポートせず、++または--の動作をd回しか実行できない.明らかにadvance関数は反復器タイプに従って再ロードする必要がある.
STLの実装を見てみましょう
//1.
template <class _InputIterator, class _Distance>
inline void advance(_InputIterator& __i, _Distance __n) {
  __STL_REQUIRES(_InputIterator, _InputIterator);
  __advance(__i, __n, iterator_category(__i));
}

外部インタフェースadvanceは、テンプレートに2つのclassがあり、1つは反復器タイプであり、1つはステップdistanceタイプである__advanceを呼び出します.__advanceを置いてまず見ないで、1つの関数iterator_categoryに気づきます
//2.
template <class _Iter>
inline typename iterator_traits<_iter>::iterator_category
iterator_category(const _Iter& __i) { return __iterator_category(__i); }

関数の戻り値に関係なく、外部インタフェースiterator_category__iterator_categoryを移動した.
//3.
template <class _Iter>
inline typename iterator_traits<_iter>::iterator_category//           
__iterator_category(const _Iter&)
{
  typedef typename iterator_traits<_iter>::iterator_category _Category;
  return _Category();//           int()       int  。
}
Iterator_categoryは内部呼び出しによって最終的にiteratorを返すことができることを示した.categorの一時オブジェクトで、この一時オブジェクトは__advanceの3番目の実パラメータとして使用されます.template typename iterator_traits<_iter>::iterator_categoryはどうやって来たのか見てみましょう
//4.
template <class _Iterator>
struct iterator_traits {
  typedef typename _Iterator::iterator_category iterator_category;
  typedef typename _Iterator::value_type        value_type;
  typedef typename _Iterator::difference_type   difference_type;
  typedef typename _Iterator::pointer           pointer;
  typedef typename _Iterator::reference         reference;
};

//         
template <class _Tp>
struct iterator_traits<_tp> {
  typedef random_access_iterator_tag iterator_category;
  typedef _Tp                         value_type;
  typedef ptrdiff_t                   difference_type;
  typedef _Tp*                        pointer;
  typedef _Tp&                        reference;
};

//const         
template <class _Tp>
struct iterator_traits<const _Tp*> {
  typedef random_access_iterator_tag iterator_category;
  typedef _Tp                         value_type;
  typedef ptrdiff_t                   difference_type;
  typedef const _Tp*                  pointer;
  typedef const _Tp&                  reference;
};

オリジナルポインタの反復器タイプは双方向ランダムアクセスをサポートするため、random_access_iterator_tagであり、他の反復器、例えばvector::iteratorのタイプはclassで宣言されており、ここでは::を直接使用してアクセスすればよい.
ここで、__advanceの内部実装にかかわらず、論理全体を見てみましょう.Advanceインタフェース呼び出し_advance 2.で_advanceにiteratorが1つありますcategory(i)パラメータ3.iterator_Category(i)が呼び出されました_iterator_category(i) 4.__iterator_categoryはiによってテンプレートタイプを推定しiterator_を呼び出すtraits反復器のiteratorを取得category型別.5.上記のカテゴリの一時オブジェクトを返します.
最後に見てみましょうadvanceの実装は、前述したように、すべてのiteratorが+=操作をサポートしているわけではないため、これは重荷のプロセスに違いありません.
//__advance     ,      ,      (     )      
//   __advance  input_iterator
template <class _InputIter, class _Distance>
inline void __advance(_InputIter& __i, _Distance __n, input_iterator_tag) {
  while (__n--) ++__i;
}
//BidirectionalIterator    
template <class _BidirectionalIterator, class _Distance>
inline void __advance(_BidirectionalIterator& __i, _Distance __n, 
                      bidirectional_iterator_tag) {
  __STL_REQUIRES(_BidirectionalIterator, _BidirectionalIterator);
  if (__n >= 0)//    
    while (__n--) ++__i;
  else
    while (__n++) --__i;
}
//RandomAccessIterator    
template <class _RandomAccessIterator, class _Distance>
inline void __advance(_RandomAccessIterator& __i, _Distance __n, 
                      random_access_iterator_tag) {
  __STL_REQUIRES(_RandomAccessIterator, _RandomAccessIterator);
  __i += __n;
}

3番目のパラメータは一時変数を返しますが、リロードされたバージョンでは使用されず、タイプによってリロードされます.
では質問です、Forwardバージョンの_アドバンスはどこだ?元は継承関係のため、パラメータが完全に一致しない場合、呼び出しInputのバージョンが自動的に渡されます.
最後に、traitsを使用してiteratorを取得すると述べました.categoryは、次のようなアクセス順序です.
list<>::iterator::iterator_category

STLにはこのようなコードがあるはずです.
template <>
class list{
public:
    class iterator{
        typedef bidirectional_iterator_tag iterator_category;
    };
};

条項48:templateメタプログラミングを認識する
以前は本当に接触したことがなくて、テンプレートの元編程は本当にamazingです.まず、階乗を求める例を見てみましょう.
template <unsigned n>
struct Factorial{
    enum { value = n*Factorial1>::value };//enum hack
};
template <>
struct Factorial < 0 > {
    enum { value = 1 };//enum hack
};
int main()
{
    cout << Factorial<5>::value << endl;//120
    system("pause");
    return 0;
}

この例はテンプレートメタプログラミングの"hello world"です...
TMP(テンプレートメタプログラミング)は、動作期間をコンパイラに移行することができ、早期のエラー検出とより高い実行効率を実現することができる.