Effective C++ノート(10)—テンプレートと汎用プログラミング(二)
条項46:タイプ変換が必要な場合は、テンプレートに非メンバー関数を定義します.
前の例を考慮して、オペレータの再ロード:
条項24では、operator*を非メンバー関数として宣言することを説明します.
ただし、上記のコードをtemplate形式に変更すると、問題が発生する可能性があります.
1つ目は実パラメトリックがRationalであり,これによりTがintとして伝達される2つ目の実パラメトリックタイプはintであり,コンパイラはTタイプを推定することができず,ここでは構造関数の暗黙タイプ変換の条件を備えていない.
解決策は、友元関数を使用することです.
最初の実パラメトリックRational具現化により対応クラスが出現し,Rationalパラメータを受け入れるfriendも具現化され,暗黙的変換の条件を満たす.
しかし、クラス内で定義するとコンパイラに組み込まれる可能性があります.inlineしたくない場合は、
条項47:traits classes表現タイプ情報を使用してください
traitsを見て私はとても親切で、抽出器、先にSTLソースの剖析を勉強したとき、これに対してまだ理解していました.
STLを振り返る反復器:反復器タイプ(
なぜここはtagなのか、実はSTLでは1つのタイプでマークすればいいだけです.Effectivec++では、advanceの例を挙げると、advance関数は
STLの実装を見てみましょう
外部インタフェース
関数の戻り値に関係なく、外部インタフェース
オリジナルポインタの反復器タイプは双方向ランダムアクセスをサポートするため、
ここで、
最後に見てみましょうadvanceの実装は、前述したように、すべてのiteratorが
3番目のパラメータは一時変数を返しますが、リロードされたバージョンでは使用されず、タイプによってリロードされます.
では質問です、Forwardバージョンの_アドバンスはどこだ?元は継承関係のため、パラメータが完全に一致しない場合、呼び出しInputのバージョンが自動的に渡されます.
最後に、traitsを使用してiteratorを取得すると述べました.categoryは、次のようなアクセス順序です.
STLにはこのようなコードがあるはずです.
条項48:templateメタプログラミングを認識する
以前は本当に接触したことがなくて、テンプレートの元編程は本当にamazingです.まず、階乗を求める例を見てみましょう.
この例はテンプレートメタプログラミングの
TMP(テンプレートメタプログラミング)は、動作期間をコンパイラに移行することができ、早期のエラー検出とより高い実行効率を実現することができる.
前の例を考慮して、オペレータの再ロード:
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(テンプレートメタプログラミング)は、動作期間をコンパイラに移行することができ、早期のエラー検出とより高い実行効率を実現することができる.